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