mirror of https://github.com/boa-dev/boa.git
Browse Source
This PR introduces a new API for JavaScript builtin objects in Rust (such as `Array`, `Map`, `Proxy`, etc). Rather than just expose the raw builtin functions as discussed here #1692 (though having raw API exposed may be nice as well), In this PR we introduce a very light wrapper around the raw API, for a more pleasant user experience. The wrapper implements functions that are specific to the wrapper type (for `Array` this would be methods like `pop`, `push`, etc) as well as implementing `Deref<Target = JsObject>` so we can call `JsObject` functions without converting to `JsObject` and `Into<JsValue>` for easy to `JsValue` conversions. Please check `jsarray.rs` in the `examples`pull/1855/head
Halid Odat
3 years ago
4 changed files with 480 additions and 1 deletions
@ -0,0 +1,107 @@ |
|||||||
|
use boa::{ |
||||||
|
object::{FunctionBuilder, JsArray}, |
||||||
|
Context, JsValue, |
||||||
|
}; |
||||||
|
|
||||||
|
fn main() -> Result<(), JsValue> { |
||||||
|
// We create a new `Context` to create a new Javascript executor.
|
||||||
|
let context = &mut Context::default(); |
||||||
|
|
||||||
|
// Create an empty array.
|
||||||
|
let array = JsArray::new(context); |
||||||
|
|
||||||
|
assert!(array.is_empty(context)?); |
||||||
|
|
||||||
|
array.push("Hello, world", context)?; // [ "Hello, world" ]
|
||||||
|
array.push(true, context)?; // [ "Hello, world", true ]
|
||||||
|
|
||||||
|
assert!(!array.is_empty(context)?); |
||||||
|
|
||||||
|
assert_eq!(array.pop(context)?, JsValue::new(true)); // [ "Hello, world" ]
|
||||||
|
assert_eq!(array.pop(context)?, JsValue::new("Hello, world")); // [ ]
|
||||||
|
assert_eq!(array.pop(context)?, JsValue::undefined()); // [ ]
|
||||||
|
|
||||||
|
array.push(1, context)?; // [ 1 ]
|
||||||
|
|
||||||
|
assert_eq!(array.pop(context)?, JsValue::new(1)); // [ ]
|
||||||
|
assert_eq!(array.pop(context)?, JsValue::undefined()); // [ ]
|
||||||
|
|
||||||
|
array.push_items( |
||||||
|
&[ |
||||||
|
JsValue::new(10), |
||||||
|
JsValue::new(11), |
||||||
|
JsValue::new(12), |
||||||
|
JsValue::new(13), |
||||||
|
JsValue::new(14), |
||||||
|
], |
||||||
|
context, |
||||||
|
)?; // [ 10, 11, 12, 13, 14 ]
|
||||||
|
|
||||||
|
array.reverse(context)?; // [ 14, 13, 12, 11, 10 ]
|
||||||
|
|
||||||
|
assert_eq!(array.index_of(12, None, context)?, Some(2)); |
||||||
|
|
||||||
|
// We can also use JsObject method `.get()` through the Deref trait.
|
||||||
|
let element = array.get(2, context)?; // array[ 0 ]
|
||||||
|
assert_eq!(element, JsValue::new(12)); |
||||||
|
// Or we can use the `.at(index)` method.
|
||||||
|
assert_eq!(array.at(0, context)?, JsValue::new(14)); // first element
|
||||||
|
assert_eq!(array.at(-1, context)?, JsValue::new(10)); // last element
|
||||||
|
|
||||||
|
// Join the array with an optional separator (default ",").
|
||||||
|
let joined_array = array.join(None, context)?; |
||||||
|
assert_eq!(joined_array, "14,13,12,11,10"); |
||||||
|
|
||||||
|
array.fill(false, Some(1), Some(4), context)?; |
||||||
|
|
||||||
|
let joined_array = array.join(Some("::".into()), context)?; |
||||||
|
assert_eq!(joined_array, "14::false::false::false::10"); |
||||||
|
|
||||||
|
let filter_callback = FunctionBuilder::native(context, |_this, args, _context| { |
||||||
|
Ok(args.get(0).cloned().unwrap_or_default().is_number().into()) |
||||||
|
}) |
||||||
|
.build(); |
||||||
|
|
||||||
|
let map_callback = FunctionBuilder::native(context, |_this, args, context| { |
||||||
|
args.get(0) |
||||||
|
.cloned() |
||||||
|
.unwrap_or_default() |
||||||
|
.pow(&JsValue::new(2), context) |
||||||
|
}) |
||||||
|
.build(); |
||||||
|
|
||||||
|
let mut data = Vec::new(); |
||||||
|
for i in 1..=5 { |
||||||
|
data.push(JsValue::new(i)); |
||||||
|
} |
||||||
|
let another_array = JsArray::from_iter(data, context); // [ 1, 2, 3, 4, 5]
|
||||||
|
|
||||||
|
let chained_array = array // [ 14, false, false, false, 10 ]
|
||||||
|
.filter(filter_callback, None, context)? // [ 14, 10 ]
|
||||||
|
.map(map_callback, None, context)? // [ 196, 100 ]
|
||||||
|
.sort(None, context)? // [ 100, 196 ]
|
||||||
|
.concat(&[another_array.into()], context)? // [ 100, 196, 1, 2, 3, 4, 5 ]
|
||||||
|
.slice(Some(1), Some(5), context)?; // [ 196, 1, 2, 3 ]
|
||||||
|
|
||||||
|
assert_eq!(chained_array.join(None, context)?, "196,1,2,3"); |
||||||
|
|
||||||
|
let reduce_callback = FunctionBuilder::native(context, |_this, args, context| { |
||||||
|
let accumulator = args.get(0).cloned().unwrap_or_default(); |
||||||
|
let value = args.get(1).cloned().unwrap_or_default(); |
||||||
|
|
||||||
|
accumulator.add(&value, context) |
||||||
|
}) |
||||||
|
.build(); |
||||||
|
|
||||||
|
assert_eq!( |
||||||
|
chained_array.reduce(reduce_callback, Some(JsValue::new(0)), context)?, |
||||||
|
JsValue::new(202) |
||||||
|
); |
||||||
|
|
||||||
|
context |
||||||
|
.global_object() |
||||||
|
.clone() |
||||||
|
.set("myArray", array, true, context)?; |
||||||
|
|
||||||
|
Ok(()) |
||||||
|
} |
@ -0,0 +1,364 @@ |
|||||||
|
use std::ops::Deref; |
||||||
|
|
||||||
|
use crate::{ |
||||||
|
builtins::Array, |
||||||
|
gc::{Finalize, Trace}, |
||||||
|
object::{JsObject, JsObjectType}, |
||||||
|
Context, JsResult, JsString, JsValue, |
||||||
|
}; |
||||||
|
|
||||||
|
/// JavaScript `Array` rust object.
|
||||||
|
#[derive(Debug, Clone, Trace, Finalize)] |
||||||
|
pub struct JsArray { |
||||||
|
inner: JsObject, |
||||||
|
} |
||||||
|
|
||||||
|
impl JsArray { |
||||||
|
/// Create a new empty array.
|
||||||
|
#[inline] |
||||||
|
pub fn new(context: &mut Context) -> Self { |
||||||
|
let inner = Array::array_create(0, None, context) |
||||||
|
.expect("creating an empty array with the default prototype must not fail"); |
||||||
|
|
||||||
|
Self { inner } |
||||||
|
} |
||||||
|
|
||||||
|
/// Create an array from a `IntoIterator<Item = JsValue>` convertable object.
|
||||||
|
#[inline] |
||||||
|
pub fn from_iter<I>(elements: I, context: &mut Context) -> Self |
||||||
|
where |
||||||
|
I: IntoIterator<Item = JsValue>, |
||||||
|
{ |
||||||
|
Self { |
||||||
|
inner: Array::create_array_from_list(elements, context), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Create an array from a `JsObject`, if the object is not an array throw a `TypeError`.
|
||||||
|
///
|
||||||
|
/// This does not copy the fields of the array, it only does a shallow copy.
|
||||||
|
#[inline] |
||||||
|
pub fn from_object(object: JsObject, context: &mut Context) -> JsResult<Self> { |
||||||
|
if object.borrow().is_array() { |
||||||
|
Ok(Self { inner: object }) |
||||||
|
} else { |
||||||
|
context.throw_type_error("object is not an Array") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Get the length of the array.
|
||||||
|
///
|
||||||
|
/// Same a `array.length` in JavaScript.
|
||||||
|
#[inline] |
||||||
|
pub fn length(&self, context: &mut Context) -> JsResult<usize> { |
||||||
|
self.inner.length_of_array_like(context) |
||||||
|
} |
||||||
|
|
||||||
|
/// Check if the array is empty, i.e. the `length` is zero.
|
||||||
|
#[inline] |
||||||
|
pub fn is_empty(&self, context: &mut Context) -> JsResult<bool> { |
||||||
|
self.inner.length_of_array_like(context).map(|len| len == 0) |
||||||
|
} |
||||||
|
|
||||||
|
/// Push an element to the array.
|
||||||
|
#[inline] |
||||||
|
pub fn push<T>(&self, value: T, context: &mut Context) -> JsResult<JsValue> |
||||||
|
where |
||||||
|
T: Into<JsValue>, |
||||||
|
{ |
||||||
|
self.push_items(&[value.into()], context) |
||||||
|
} |
||||||
|
|
||||||
|
/// Pushes a slice of elements to the array.
|
||||||
|
#[inline] |
||||||
|
pub fn push_items(&self, items: &[JsValue], context: &mut Context) -> JsResult<JsValue> { |
||||||
|
Array::push(&self.inner.clone().into(), items, context) |
||||||
|
} |
||||||
|
|
||||||
|
/// Pops an element from the array.
|
||||||
|
#[inline] |
||||||
|
pub fn pop(&self, context: &mut Context) -> JsResult<JsValue> { |
||||||
|
Array::pop(&self.inner.clone().into(), &[], context) |
||||||
|
} |
||||||
|
|
||||||
|
#[inline] |
||||||
|
pub fn at<T>(&self, index: T, context: &mut Context) -> JsResult<JsValue> |
||||||
|
where |
||||||
|
T: Into<i64>, |
||||||
|
{ |
||||||
|
Array::at(&self.inner.clone().into(), &[index.into().into()], context) |
||||||
|
} |
||||||
|
|
||||||
|
#[inline] |
||||||
|
pub fn shift(&self, context: &mut Context) -> JsResult<JsValue> { |
||||||
|
Array::shift(&self.inner.clone().into(), &[], context) |
||||||
|
} |
||||||
|
|
||||||
|
#[inline] |
||||||
|
pub fn unshift(&self, items: &[JsValue], context: &mut Context) -> JsResult<JsValue> { |
||||||
|
Array::shift(&self.inner.clone().into(), items, context) |
||||||
|
} |
||||||
|
|
||||||
|
#[inline] |
||||||
|
pub fn reverse(&self, context: &mut Context) -> JsResult<Self> { |
||||||
|
Array::reverse(&self.inner.clone().into(), &[], context)?; |
||||||
|
Ok(self.clone()) |
||||||
|
} |
||||||
|
|
||||||
|
#[inline] |
||||||
|
pub fn concat(&self, items: &[JsValue], context: &mut Context) -> JsResult<Self> { |
||||||
|
let object = Array::concat(&self.inner.clone().into(), items, context)? |
||||||
|
.as_object() |
||||||
|
.cloned() |
||||||
|
.expect("Array.prototype.filter should always return object"); |
||||||
|
|
||||||
|
Self::from_object(object, context) |
||||||
|
} |
||||||
|
|
||||||
|
#[inline] |
||||||
|
pub fn join(&self, separator: Option<JsString>, context: &mut Context) -> JsResult<JsString> { |
||||||
|
Array::join(&self.inner.clone().into(), &[separator.into()], context).map(|x| { |
||||||
|
x.as_string() |
||||||
|
.cloned() |
||||||
|
.expect("Array.prototype.join always returns string") |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
#[inline] |
||||||
|
pub fn fill<T>( |
||||||
|
&self, |
||||||
|
value: T, |
||||||
|
start: Option<u32>, |
||||||
|
end: Option<u32>, |
||||||
|
context: &mut Context, |
||||||
|
) -> JsResult<Self> |
||||||
|
where |
||||||
|
T: Into<JsValue>, |
||||||
|
{ |
||||||
|
Array::fill( |
||||||
|
&self.inner.clone().into(), |
||||||
|
&[value.into(), start.into(), end.into()], |
||||||
|
context, |
||||||
|
)?; |
||||||
|
Ok(self.clone()) |
||||||
|
} |
||||||
|
|
||||||
|
#[inline] |
||||||
|
pub fn index_of<T>( |
||||||
|
&self, |
||||||
|
search_element: T, |
||||||
|
from_index: Option<u32>, |
||||||
|
context: &mut Context, |
||||||
|
) -> JsResult<Option<u32>> |
||||||
|
where |
||||||
|
T: Into<JsValue>, |
||||||
|
{ |
||||||
|
let index = Array::index_of( |
||||||
|
&self.inner.clone().into(), |
||||||
|
&[search_element.into(), from_index.into()], |
||||||
|
context, |
||||||
|
)? |
||||||
|
.as_number() |
||||||
|
.expect("Array.prototype.indexOf should always return number"); |
||||||
|
|
||||||
|
#[allow(clippy::float_cmp)] |
||||||
|
if index == -1.0 { |
||||||
|
Ok(None) |
||||||
|
} else { |
||||||
|
Ok(Some(index as u32)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[inline] |
||||||
|
pub fn last_index_of<T>( |
||||||
|
&self, |
||||||
|
search_element: T, |
||||||
|
from_index: Option<u32>, |
||||||
|
context: &mut Context, |
||||||
|
) -> JsResult<Option<u32>> |
||||||
|
where |
||||||
|
T: Into<JsValue>, |
||||||
|
{ |
||||||
|
let index = Array::last_index_of( |
||||||
|
&self.inner.clone().into(), |
||||||
|
&[search_element.into(), from_index.into()], |
||||||
|
context, |
||||||
|
)? |
||||||
|
.as_number() |
||||||
|
.expect("Array.prototype.lastIndexOf should always return number"); |
||||||
|
|
||||||
|
#[allow(clippy::float_cmp)] |
||||||
|
if index == -1.0 { |
||||||
|
Ok(None) |
||||||
|
} else { |
||||||
|
Ok(Some(index as u32)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[inline] |
||||||
|
pub fn find( |
||||||
|
&self, |
||||||
|
predicate: JsObject, |
||||||
|
this_arg: Option<JsValue>, |
||||||
|
context: &mut Context, |
||||||
|
) -> JsResult<JsValue> { |
||||||
|
Array::find( |
||||||
|
&self.inner.clone().into(), |
||||||
|
&[predicate.into(), this_arg.into()], |
||||||
|
context, |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
#[inline] |
||||||
|
pub fn filter( |
||||||
|
&self, |
||||||
|
callback: JsObject, |
||||||
|
this_arg: Option<JsValue>, |
||||||
|
context: &mut Context, |
||||||
|
) -> JsResult<Self> { |
||||||
|
let object = Array::filter( |
||||||
|
&self.inner.clone().into(), |
||||||
|
&[callback.into(), this_arg.into()], |
||||||
|
context, |
||||||
|
)? |
||||||
|
.as_object() |
||||||
|
.cloned() |
||||||
|
.expect("Array.prototype.filter should always return object"); |
||||||
|
|
||||||
|
Self::from_object(object, context) |
||||||
|
} |
||||||
|
|
||||||
|
#[inline] |
||||||
|
pub fn map( |
||||||
|
&self, |
||||||
|
callback: JsObject, |
||||||
|
this_arg: Option<JsValue>, |
||||||
|
context: &mut Context, |
||||||
|
) -> JsResult<Self> { |
||||||
|
let object = Array::map( |
||||||
|
&self.inner.clone().into(), |
||||||
|
&[callback.into(), this_arg.into()], |
||||||
|
context, |
||||||
|
)? |
||||||
|
.as_object() |
||||||
|
.cloned() |
||||||
|
.expect("Array.prototype.map should always return object"); |
||||||
|
|
||||||
|
Self::from_object(object, context) |
||||||
|
} |
||||||
|
|
||||||
|
#[inline] |
||||||
|
pub fn every( |
||||||
|
&self, |
||||||
|
callback: JsObject, |
||||||
|
this_arg: Option<JsValue>, |
||||||
|
context: &mut Context, |
||||||
|
) -> JsResult<bool> { |
||||||
|
let result = Array::every( |
||||||
|
&self.inner.clone().into(), |
||||||
|
&[callback.into(), this_arg.into()], |
||||||
|
context, |
||||||
|
)? |
||||||
|
.as_boolean() |
||||||
|
.expect("Array.prototype.every should always return boolean"); |
||||||
|
|
||||||
|
Ok(result) |
||||||
|
} |
||||||
|
|
||||||
|
#[inline] |
||||||
|
pub fn some( |
||||||
|
&self, |
||||||
|
callback: JsObject, |
||||||
|
this_arg: Option<JsValue>, |
||||||
|
context: &mut Context, |
||||||
|
) -> JsResult<bool> { |
||||||
|
let result = Array::some( |
||||||
|
&self.inner.clone().into(), |
||||||
|
&[callback.into(), this_arg.into()], |
||||||
|
context, |
||||||
|
)? |
||||||
|
.as_boolean() |
||||||
|
.expect("Array.prototype.some should always return boolean"); |
||||||
|
|
||||||
|
Ok(result) |
||||||
|
} |
||||||
|
|
||||||
|
#[inline] |
||||||
|
pub fn sort(&self, compare_fn: Option<JsObject>, context: &mut Context) -> JsResult<Self> { |
||||||
|
Array::sort(&self.inner.clone().into(), &[compare_fn.into()], context)?; |
||||||
|
|
||||||
|
Ok(self.clone()) |
||||||
|
} |
||||||
|
|
||||||
|
#[inline] |
||||||
|
pub fn slice( |
||||||
|
&self, |
||||||
|
start: Option<u32>, |
||||||
|
end: Option<u32>, |
||||||
|
context: &mut Context, |
||||||
|
) -> JsResult<Self> { |
||||||
|
let object = Array::slice( |
||||||
|
&self.inner.clone().into(), |
||||||
|
&[start.into(), end.into()], |
||||||
|
context, |
||||||
|
)? |
||||||
|
.as_object() |
||||||
|
.cloned() |
||||||
|
.expect("Array.prototype.slice should always return object"); |
||||||
|
|
||||||
|
Self::from_object(object, context) |
||||||
|
} |
||||||
|
|
||||||
|
#[inline] |
||||||
|
pub fn reduce( |
||||||
|
&self, |
||||||
|
callback: JsObject, |
||||||
|
initial_value: Option<JsValue>, |
||||||
|
context: &mut Context, |
||||||
|
) -> JsResult<JsValue> { |
||||||
|
Array::reduce( |
||||||
|
&self.inner.clone().into(), |
||||||
|
&[callback.into(), initial_value.into()], |
||||||
|
context, |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
#[inline] |
||||||
|
pub fn reduce_right( |
||||||
|
&self, |
||||||
|
callback: JsObject, |
||||||
|
initial_value: Option<JsValue>, |
||||||
|
context: &mut Context, |
||||||
|
) -> JsResult<JsValue> { |
||||||
|
Array::reduce_right( |
||||||
|
&self.inner.clone().into(), |
||||||
|
&[callback.into(), initial_value.into()], |
||||||
|
context, |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl From<JsArray> for JsObject { |
||||||
|
#[inline] |
||||||
|
fn from(o: JsArray) -> Self { |
||||||
|
o.inner.clone() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl From<JsArray> for JsValue { |
||||||
|
#[inline] |
||||||
|
fn from(o: JsArray) -> Self { |
||||||
|
o.inner.clone().into() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl Deref for JsArray { |
||||||
|
type Target = JsObject; |
||||||
|
|
||||||
|
#[inline] |
||||||
|
fn deref(&self) -> &Self::Target { |
||||||
|
&self.inner |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl JsObjectType for JsArray {} |
Loading…
Reference in new issue