mirror of https://github.com/boa-dev/boa.git
Browse Source
This PR adds a wrapper around the raw builtins for JavaScript typed arrays, like #1746pull/2031/head
Halid Odat
2 years ago
5 changed files with 591 additions and 33 deletions
@ -0,0 +1,394 @@ |
|||||||
|
use crate::{ |
||||||
|
builtins::typed_array::TypedArray, |
||||||
|
object::{JsArray, JsObject, JsObjectType}, |
||||||
|
value::IntoOrUndefined, |
||||||
|
Context, JsResult, JsString, JsValue, |
||||||
|
}; |
||||||
|
use boa_gc::{Finalize, Trace}; |
||||||
|
use std::ops::Deref; |
||||||
|
|
||||||
|
/// JavaScript `TypedArray` rust object.
|
||||||
|
#[derive(Debug, Clone, Trace, Finalize)] |
||||||
|
pub struct JsTypedArray { |
||||||
|
inner: JsValue, |
||||||
|
} |
||||||
|
|
||||||
|
impl JsTypedArray { |
||||||
|
#[inline] |
||||||
|
pub fn from_object(object: JsObject, context: &mut Context) -> JsResult<Self> { |
||||||
|
if object.borrow().is_typed_array() { |
||||||
|
Ok(Self { |
||||||
|
inner: object.into(), |
||||||
|
}) |
||||||
|
} else { |
||||||
|
context.throw_type_error("object is not an TypedArray") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Get the length of the array.
|
||||||
|
///
|
||||||
|
/// Same a `array.length` in JavaScript.
|
||||||
|
#[inline] |
||||||
|
pub fn length(&self, context: &mut Context) -> JsResult<usize> { |
||||||
|
Ok(TypedArray::length(&self.inner, &[], context)? |
||||||
|
.as_number() |
||||||
|
.map(|x| x as usize) |
||||||
|
.expect("length should return a number")) |
||||||
|
} |
||||||
|
|
||||||
|
/// Check if the array is empty, i.e. the `length` is zero.
|
||||||
|
#[inline] |
||||||
|
pub fn is_empty(&self, context: &mut Context) -> JsResult<bool> { |
||||||
|
Ok(self.length(context)? == 0) |
||||||
|
} |
||||||
|
|
||||||
|
#[inline] |
||||||
|
pub fn at<T>(&self, index: T, context: &mut Context) -> JsResult<JsValue> |
||||||
|
where |
||||||
|
T: Into<i64>, |
||||||
|
{ |
||||||
|
TypedArray::at(&self.inner, &[index.into().into()], context) |
||||||
|
} |
||||||
|
|
||||||
|
#[inline] |
||||||
|
pub fn byte_length(&self, context: &mut Context) -> JsResult<usize> { |
||||||
|
Ok(TypedArray::byte_length(&self.inner, &[], context)? |
||||||
|
.as_number() |
||||||
|
.map(|x| x as usize) |
||||||
|
.expect("byteLength should return a number")) |
||||||
|
} |
||||||
|
|
||||||
|
#[inline] |
||||||
|
pub fn byte_offset(&self, context: &mut Context) -> JsResult<usize> { |
||||||
|
Ok(TypedArray::byte_offset(&self.inner, &[], context)? |
||||||
|
.as_number() |
||||||
|
.map(|x| x as usize) |
||||||
|
.expect("byteLength should return a number")) |
||||||
|
} |
||||||
|
|
||||||
|
#[inline] |
||||||
|
pub fn fill<T>( |
||||||
|
&self, |
||||||
|
value: T, |
||||||
|
start: Option<usize>, |
||||||
|
end: Option<usize>, |
||||||
|
context: &mut Context, |
||||||
|
) -> JsResult<Self> |
||||||
|
where |
||||||
|
T: Into<JsValue>, |
||||||
|
{ |
||||||
|
TypedArray::fill( |
||||||
|
&self.inner, |
||||||
|
&[ |
||||||
|
value.into(), |
||||||
|
start.into_or_undefined(), |
||||||
|
end.into_or_undefined(), |
||||||
|
], |
||||||
|
context, |
||||||
|
)?; |
||||||
|
Ok(self.clone()) |
||||||
|
} |
||||||
|
|
||||||
|
pub fn every( |
||||||
|
&self, |
||||||
|
predicate: JsObject, |
||||||
|
this_arg: Option<JsValue>, |
||||||
|
context: &mut Context, |
||||||
|
) -> JsResult<bool> { |
||||||
|
let result = TypedArray::every( |
||||||
|
&self.inner, |
||||||
|
&[predicate.into(), this_arg.into_or_undefined()], |
||||||
|
context, |
||||||
|
)? |
||||||
|
.as_boolean() |
||||||
|
.expect("TypedArray.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 = TypedArray::some( |
||||||
|
&self.inner, |
||||||
|
&[callback.into(), this_arg.into_or_undefined()], |
||||||
|
context, |
||||||
|
)? |
||||||
|
.as_boolean() |
||||||
|
.expect("TypedArray.prototype.some should always return boolean"); |
||||||
|
|
||||||
|
Ok(result) |
||||||
|
} |
||||||
|
|
||||||
|
#[inline] |
||||||
|
pub fn sort(&self, compare_fn: Option<JsObject>, context: &mut Context) -> JsResult<Self> { |
||||||
|
TypedArray::sort(&self.inner, &[compare_fn.into_or_undefined()], context)?; |
||||||
|
|
||||||
|
Ok(self.clone()) |
||||||
|
} |
||||||
|
|
||||||
|
#[inline] |
||||||
|
pub fn filter( |
||||||
|
&self, |
||||||
|
callback: JsObject, |
||||||
|
this_arg: Option<JsValue>, |
||||||
|
context: &mut Context, |
||||||
|
) -> JsResult<Self> { |
||||||
|
let object = TypedArray::filter( |
||||||
|
&self.inner, |
||||||
|
&[callback.into(), this_arg.into_or_undefined()], |
||||||
|
context, |
||||||
|
)?; |
||||||
|
|
||||||
|
Ok(Self { inner: object }) |
||||||
|
} |
||||||
|
|
||||||
|
#[inline] |
||||||
|
pub fn map( |
||||||
|
&self, |
||||||
|
callback: JsObject, |
||||||
|
this_arg: Option<JsValue>, |
||||||
|
context: &mut Context, |
||||||
|
) -> JsResult<Self> { |
||||||
|
let object = TypedArray::map( |
||||||
|
&self.inner, |
||||||
|
&[callback.into(), this_arg.into_or_undefined()], |
||||||
|
context, |
||||||
|
)?; |
||||||
|
|
||||||
|
Ok(Self { inner: object }) |
||||||
|
} |
||||||
|
|
||||||
|
#[inline] |
||||||
|
pub fn reduce( |
||||||
|
&self, |
||||||
|
callback: JsObject, |
||||||
|
initial_value: Option<JsValue>, |
||||||
|
context: &mut Context, |
||||||
|
) -> JsResult<JsValue> { |
||||||
|
TypedArray::reduce( |
||||||
|
&self.inner, |
||||||
|
&[callback.into(), initial_value.into_or_undefined()], |
||||||
|
context, |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
#[inline] |
||||||
|
pub fn reduce_right( |
||||||
|
&self, |
||||||
|
callback: JsObject, |
||||||
|
initial_value: Option<JsValue>, |
||||||
|
context: &mut Context, |
||||||
|
) -> JsResult<JsValue> { |
||||||
|
TypedArray::reduceright( |
||||||
|
&self.inner, |
||||||
|
&[callback.into(), initial_value.into_or_undefined()], |
||||||
|
context, |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
#[inline] |
||||||
|
pub fn reverse(&self, context: &mut Context) -> JsResult<Self> { |
||||||
|
TypedArray::reverse(&self.inner, &[], context)?; |
||||||
|
Ok(self.clone()) |
||||||
|
} |
||||||
|
|
||||||
|
#[inline] |
||||||
|
pub fn slice( |
||||||
|
&self, |
||||||
|
start: Option<usize>, |
||||||
|
end: Option<usize>, |
||||||
|
context: &mut Context, |
||||||
|
) -> JsResult<Self> { |
||||||
|
let object = TypedArray::slice( |
||||||
|
&self.inner, |
||||||
|
&[start.into_or_undefined(), end.into_or_undefined()], |
||||||
|
context, |
||||||
|
)?; |
||||||
|
|
||||||
|
Ok(Self { inner: object }) |
||||||
|
} |
||||||
|
|
||||||
|
#[inline] |
||||||
|
pub fn find( |
||||||
|
&self, |
||||||
|
predicate: JsObject, |
||||||
|
this_arg: Option<JsValue>, |
||||||
|
context: &mut Context, |
||||||
|
) -> JsResult<JsValue> { |
||||||
|
TypedArray::find( |
||||||
|
&self.inner, |
||||||
|
&[predicate.into(), this_arg.into_or_undefined()], |
||||||
|
context, |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
#[inline] |
||||||
|
pub fn index_of<T>( |
||||||
|
&self, |
||||||
|
search_element: T, |
||||||
|
from_index: Option<usize>, |
||||||
|
context: &mut Context, |
||||||
|
) -> JsResult<Option<usize>> |
||||||
|
where |
||||||
|
T: Into<JsValue>, |
||||||
|
{ |
||||||
|
let index = TypedArray::index_of( |
||||||
|
&self.inner, |
||||||
|
&[search_element.into(), from_index.into_or_undefined()], |
||||||
|
context, |
||||||
|
)? |
||||||
|
.as_number() |
||||||
|
.expect("TypedArray.prototype.indexOf should always return number"); |
||||||
|
|
||||||
|
#[allow(clippy::float_cmp)] |
||||||
|
if index == -1.0 { |
||||||
|
Ok(None) |
||||||
|
} else { |
||||||
|
Ok(Some(index as usize)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[inline] |
||||||
|
pub fn last_index_of<T>( |
||||||
|
&self, |
||||||
|
search_element: T, |
||||||
|
from_index: Option<usize>, |
||||||
|
context: &mut Context, |
||||||
|
) -> JsResult<Option<usize>> |
||||||
|
where |
||||||
|
T: Into<JsValue>, |
||||||
|
{ |
||||||
|
let index = TypedArray::last_index_of( |
||||||
|
&self.inner, |
||||||
|
&[search_element.into(), from_index.into_or_undefined()], |
||||||
|
context, |
||||||
|
)? |
||||||
|
.as_number() |
||||||
|
.expect("TypedArray.prototype.lastIndexOf should always return number"); |
||||||
|
|
||||||
|
#[allow(clippy::float_cmp)] |
||||||
|
if index == -1.0 { |
||||||
|
Ok(None) |
||||||
|
} else { |
||||||
|
Ok(Some(index as usize)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[inline] |
||||||
|
pub fn join(&self, separator: Option<JsString>, context: &mut Context) -> JsResult<JsString> { |
||||||
|
TypedArray::join(&self.inner, &[separator.into_or_undefined()], context).map(|x| { |
||||||
|
x.as_string() |
||||||
|
.cloned() |
||||||
|
.expect("TypedArray.prototype.join always returns string") |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl From<JsTypedArray> for JsObject { |
||||||
|
#[inline] |
||||||
|
fn from(o: JsTypedArray) -> Self { |
||||||
|
o.inner |
||||||
|
.as_object() |
||||||
|
.expect("should always be an object") |
||||||
|
.clone() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl From<JsTypedArray> for JsValue { |
||||||
|
#[inline] |
||||||
|
fn from(o: JsTypedArray) -> Self { |
||||||
|
o.inner.clone() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl Deref for JsTypedArray { |
||||||
|
type Target = JsObject; |
||||||
|
|
||||||
|
#[inline] |
||||||
|
fn deref(&self) -> &Self::Target { |
||||||
|
self.inner.as_object().expect("should always be an object") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl JsObjectType for JsTypedArray {} |
||||||
|
|
||||||
|
macro_rules! JsTypedArrayType { |
||||||
|
($name:ident, $constructor_function:ident, $constructor_object:ident, $element:ty) => { |
||||||
|
#[doc = concat!("JavaScript `", stringify!($constructor_function), "` rust object.")] |
||||||
|
#[derive(Debug, Clone, Trace, Finalize)] |
||||||
|
pub struct $name { |
||||||
|
inner: JsTypedArray, |
||||||
|
} |
||||||
|
|
||||||
|
impl $name { |
||||||
|
#[inline] |
||||||
|
pub fn from_iter<I>(elements: I, context: &mut Context) -> JsResult<Self> |
||||||
|
where |
||||||
|
I: IntoIterator<Item = $element>, |
||||||
|
{ |
||||||
|
let array = JsArray::from_iter(elements.into_iter().map(JsValue::new), context); |
||||||
|
let new_target = context |
||||||
|
.intrinsics() |
||||||
|
.constructors() |
||||||
|
.$constructor_object() |
||||||
|
.constructor() |
||||||
|
.into(); |
||||||
|
let object = crate::builtins::typed_array::$constructor_function::constructor( |
||||||
|
&new_target, |
||||||
|
&[array.into()], |
||||||
|
context, |
||||||
|
)? |
||||||
|
.as_object() |
||||||
|
.expect("object") |
||||||
|
.clone(); |
||||||
|
|
||||||
|
Ok(Self { |
||||||
|
inner: JsTypedArray { |
||||||
|
inner: object.into(), |
||||||
|
}, |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl From<$name> for JsObject { |
||||||
|
#[inline] |
||||||
|
fn from(o: $name) -> Self { |
||||||
|
o.inner |
||||||
|
.inner |
||||||
|
.as_object() |
||||||
|
.expect("should always be an object") |
||||||
|
.clone() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl From<$name> for JsValue { |
||||||
|
#[inline] |
||||||
|
fn from(o: $name) -> Self { |
||||||
|
o.inner.inner.clone() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl Deref for $name { |
||||||
|
type Target = JsTypedArray; |
||||||
|
|
||||||
|
#[inline] |
||||||
|
fn deref(&self) -> &Self::Target { |
||||||
|
&self.inner |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
JsTypedArrayType!(JsUint8Array, Uint8Array, typed_uint8_array, u8); |
||||||
|
JsTypedArrayType!(JsInt8Array, Int8Array, typed_int8_array, i8); |
||||||
|
JsTypedArrayType!(JsUint16Array, Uint16Array, typed_uint16_array, u16); |
||||||
|
JsTypedArrayType!(JsInt16Array, Int16Array, typed_int16_array, i16); |
||||||
|
JsTypedArrayType!(JsUint32Array, Uint32Array, typed_uint32_array, u32); |
||||||
|
JsTypedArrayType!(JsInt32Array, Int32Array, typed_int32_array, i32); |
||||||
|
JsTypedArrayType!(JsFloat32Array, Float32Array, typed_float32_array, f32); |
||||||
|
JsTypedArrayType!(JsFloat64Array, Float64Array, typed_float64_array, f64); |
@ -0,0 +1,46 @@ |
|||||||
|
// This example shows how to manipulate a Javascript array using Rust code.
|
||||||
|
|
||||||
|
use boa_engine::{ |
||||||
|
object::{FunctionBuilder, JsUint8Array}, |
||||||
|
property::Attribute, |
||||||
|
Context, JsResult, JsValue, |
||||||
|
}; |
||||||
|
|
||||||
|
fn main() -> JsResult<()> { |
||||||
|
// We create a new `Context` to create a new Javascript executor.
|
||||||
|
let context = &mut Context::default(); |
||||||
|
|
||||||
|
let data: Vec<u8> = (0..=255).collect(); |
||||||
|
|
||||||
|
let array = JsUint8Array::from_iter(data, context)?; |
||||||
|
|
||||||
|
assert_eq!(array.get(0, context)?, JsValue::new(0)); |
||||||
|
|
||||||
|
let mut sum = 0; |
||||||
|
|
||||||
|
for i in 0..=255 { |
||||||
|
assert_eq!(array.at(i, context)?, JsValue::new(i)); |
||||||
|
sum += i; |
||||||
|
} |
||||||
|
|
||||||
|
let 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!( |
||||||
|
array.reduce(callback, Some(JsValue::new(0)), context)?, |
||||||
|
JsValue::new(sum) |
||||||
|
); |
||||||
|
|
||||||
|
context.register_global_property( |
||||||
|
"myUint8Array", |
||||||
|
array, |
||||||
|
Attribute::WRITABLE | Attribute::ENUMERABLE | Attribute::CONFIGURABLE, |
||||||
|
); |
||||||
|
|
||||||
|
Ok(()) |
||||||
|
} |
Loading…
Reference in new issue