diff --git a/boa/examples/jsarray.rs b/boa/examples/jsarray.rs index 1a94ca27e41..04928b2da32 100644 --- a/boa/examples/jsarray.rs +++ b/boa/examples/jsarray.rs @@ -1,7 +1,7 @@ -// This example goes into the details on how to pass closures as functions -// inside Rust and call them from Javascript. - -use boa::{object::JsArray, Context, JsValue}; +use boa::{ + object::{FunctionBuilder, JsArray}, + Context, JsValue, +}; fn main() -> Result<(), JsValue> { // We create a new `Context` to create a new Javascript executor. @@ -9,8 +9,15 @@ fn main() -> Result<(), JsValue> { // Create a new array with 2 elements. // - // [ "Hello, world", true ] - let array = JsArray::from_list(&[JsValue::new("Hello, world"), JsValue::new(true)], context); + // + + // Create an empty array. + let array = JsArray::empty(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)?); @@ -26,29 +33,74 @@ fn main() -> Result<(), JsValue> { array.push_items( &[ JsValue::new(10), + JsValue::new(11), JsValue::new(12), JsValue::new(13), JsValue::new(14), ], context, - )?; // [ 10, 12, 13, 14 ] + )?; // [ 10, 11, 12, 13, 14 ] - array.reverse(context)?; // [ 14, 13, 12, 10 ] + 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(0, context)?; // 0 - assert_eq!(element, JsValue::new(14)); + 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,10"); + assert_eq!(joined_array, "14,13,12,11,10"); - array.fill(false, Some(1), Some(3), context)?; + array.fill(false, Some(1), Some(4), context)?; let joined_array = array.join(Some("::".into()), context)?; - assert_eq!(joined_array, "14::false::false::10"); + 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::new(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() diff --git a/boa/src/object/jsarray.rs b/boa/src/object/jsarray.rs index 0d94df0f9bf..20387d924a0 100644 --- a/boa/src/object/jsarray.rs +++ b/boa/src/object/jsarray.rs @@ -69,6 +69,14 @@ impl JsArray { Array::pop(&self.inner.clone().into(), &[], context) } + #[inline] + pub fn at(&self, index: T, context: &mut Context) -> JsResult + where + T: Into, + { + Array::at(&self.inner.clone().into(), &[index.into().into()], context) + } + #[inline] pub fn shift(&self, context: &mut Context) -> JsResult { Array::shift(&self.inner.clone().into(), &[], context) @@ -85,6 +93,16 @@ impl JsArray { Ok(self.clone()) } + #[inline] + pub fn concat(&self, items: &[JsValue], context: &mut Context) -> JsResult { + let object = Array::concat(&self.inner.clone().into(), items, context)? + .as_object() + .cloned() + .expect("Array.prototype.filter should always return object"); + + Self::from(object, context) + } + #[inline] pub fn join(&self, separator: Option, context: &mut Context) -> JsResult { Array::join(&self.inner.clone().into(), &[separator.into()], context).map(|x| { @@ -138,6 +156,174 @@ impl JsArray { Ok(Some(index as u32)) } } + + #[inline] + pub fn last_index_of( + &self, + search_element: T, + from_index: Option, + context: &mut Context, + ) -> JsResult> + where + T: Into, + { + 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, + context: &mut Context, + ) -> JsResult { + Array::find( + &self.inner.clone().into(), + &[predicate.into(), this_arg.into()], + context, + ) + } + + #[inline] + pub fn filter( + &self, + callback: JsObject, + this_arg: Option, + context: &mut Context, + ) -> JsResult { + 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, context) + } + + #[inline] + pub fn map( + &self, + callback: JsObject, + this_arg: Option, + context: &mut Context, + ) -> JsResult { + 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, context) + } + + #[inline] + pub fn every( + &self, + callback: JsObject, + this_arg: Option, + context: &mut Context, + ) -> JsResult { + 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, + context: &mut Context, + ) -> JsResult { + 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, context: &mut Context) -> JsResult { + Array::sort(&self.inner.clone().into(), &[compare_fn.into()], context)?; + + Ok(self.clone()) + } + + #[inline] + pub fn slice( + &self, + start: Option, + end: Option, + context: &mut Context, + ) -> JsResult { + 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, context) + } + + #[inline] + pub fn reduce( + &self, + callback: JsObject, + initial_value: Option, + context: &mut Context, + ) -> JsResult { + Array::reduce( + &self.inner.clone().into(), + &[callback.into(), initial_value.into()], + context, + ) + } + + #[inline] + pub fn reduce_right( + &self, + callback: JsObject, + initial_value: Option, + context: &mut Context, + ) -> JsResult { + Array::reduce_right( + &self.inner.clone().into(), + &[callback.into(), initial_value.into()], + context, + ) + } } impl From for JsObject { diff --git a/boa/src/value/conversions.rs b/boa/src/value/conversions.rs index 667194642d3..fb189060d24 100644 --- a/boa/src/value/conversions.rs +++ b/boa/src/value/conversions.rs @@ -153,7 +153,7 @@ where fn from(value: Option) -> Self { match value { Some(value) => value.into(), - None => Self::null(), + None => Self::undefined(), } } }