diff --git a/boa_engine/src/builtins/typed_array/mod.rs b/boa_engine/src/builtins/typed_array/mod.rs index d7ea9c48957..a2158f2b1d7 100644 --- a/boa_engine/src/builtins/typed_array/mod.rs +++ b/boa_engine/src/builtins/typed_array/mod.rs @@ -114,7 +114,7 @@ macro_rules! typed_array { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-typedarray - fn constructor( + pub(crate) fn constructor( new_target: &JsValue, args: &[JsValue], context: &mut Context, @@ -549,7 +549,7 @@ impl TypedArray { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-%typedarray%.prototype.at - fn at(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + pub(crate) fn at(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { // 1. Let O be the this value. // 2. Perform ? ValidateTypedArray(O). let obj = this @@ -2951,7 +2951,7 @@ impl TypedArray { } /// - fn initialize_from_list( + pub(crate) fn initialize_from_list( o: &JsObject, values: Vec, context: &mut Context, diff --git a/boa_engine/src/object/jstypedarray.rs b/boa_engine/src/object/jstypedarray.rs new file mode 100644 index 00000000000..f04d59f869c --- /dev/null +++ b/boa_engine/src/object/jstypedarray.rs @@ -0,0 +1,133 @@ +use crate::{ + builtins::typed_array::TypedArray, + object::{JsArray, JsObject, JsObjectType}, + Context, JsResult, JsValue, +}; +use boa_gc::{Finalize, Trace}; +use std::ops::Deref; + +/// JavaScript `TypedArray` rust object. +#[derive(Debug, Clone, Trace, Finalize)] +pub struct JsTypedArray { + inner: JsObject, +} + +impl JsTypedArray { + /// Get the length of the array. + /// + /// Same a `array.length` in JavaScript. + #[inline] + pub fn length(&self, context: &mut Context) -> JsResult { + 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 { + self.inner.length_of_array_like(context).map(|len| len == 0) + } + + #[inline] + pub fn at(&self, index: T, context: &mut Context) -> JsResult + where + T: Into, + { + TypedArray::at(&self.inner.clone().into(), &[index.into().into()], context) + } +} + +impl From for JsObject { + #[inline] + fn from(o: JsTypedArray) -> Self { + o.inner.clone() + } +} + +impl From for JsValue { + #[inline] + fn from(o: JsTypedArray) -> Self { + o.inner.clone().into() + } +} + +impl Deref for JsTypedArray { + type Target = JsObject; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +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(elements: I, context: &mut Context) -> JsResult + where + I: IntoIterator, + { + // TODO: This is pretty inefficient, find a better way of creating typed arrays from iterators + 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 }, + }) + } + } + + impl From<$name> for JsObject { + #[inline] + fn from(o: $name) -> Self { + o.inner.inner.clone() + } + } + + impl From<$name> for JsValue { + #[inline] + fn from(o: $name) -> Self { + o.inner.inner.clone().into() + } + } + + 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); diff --git a/boa_engine/src/object/mod.rs b/boa_engine/src/object/mod.rs index 0a2a992a66a..6ad7e9daf26 100644 --- a/boa_engine/src/object/mod.rs +++ b/boa_engine/src/object/mod.rs @@ -57,10 +57,12 @@ mod tests; pub(crate) mod internal_methods; mod jsarray; mod jsobject; +mod jstypedarray; mod operations; mod property_map; pub use jsarray::*; +pub use jstypedarray::*; pub(crate) trait JsObjectType: Into + Into + Deref diff --git a/boa_engine/src/value/conversions.rs b/boa_engine/src/value/conversions.rs index ef2e7f8e8f1..771acbbf8c2 100644 --- a/boa_engine/src/value/conversions.rs +++ b/boa_engine/src/value/conversions.rs @@ -42,30 +42,58 @@ impl Display for TryFromCharError { } } -impl From for JsValue { +impl From for JsValue { #[allow(clippy::float_cmp)] #[inline] - fn from(value: f64) -> Self { + fn from(value: f32) -> Self { // if value as i32 as f64 == value { // Self::Integer(value as i32) // } else { - Self::Rational(value) + Self::Rational(value.into()) // } } } -impl From for JsValue { +impl From for JsValue { #[allow(clippy::float_cmp)] #[inline] - fn from(value: f32) -> Self { - // if value as i32 as f32 == value { + fn from(value: f64) -> Self { + // if value as i32 as f64 == value { // Self::Integer(value as i32) // } else { - Self::Rational(value.into()) + Self::Rational(value) // } } } +impl From for JsValue { + #[inline] + fn from(value: u8) -> Self { + Self::Integer(value.into()) + } +} + +impl From for JsValue { + #[inline] + fn from(value: i8) -> Self { + Self::Integer(value.into()) + } +} + +impl From for JsValue { + #[inline] + fn from(value: u16) -> Self { + Self::Integer(value.into()) + } +} + +impl From for JsValue { + #[inline] + fn from(value: i16) -> Self { + Self::Integer(value.into()) + } +} + impl From for JsValue { #[inline] fn from(value: u32) -> Self { diff --git a/boa_examples/src/bin/jstypedarray.rs b/boa_examples/src/bin/jstypedarray.rs new file mode 100644 index 00000000000..656d0b092ac --- /dev/null +++ b/boa_examples/src/bin/jstypedarray.rs @@ -0,0 +1,26 @@ +// This example shows how to manipulate a Javascript array using Rust code. + +use boa_engine::{object::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 = (0..=255).collect(); + + let array = JsUint8Array::from_iter(data, context)?; + + assert_eq!(array.get(0, context)?, JsValue::new(0)); + + for i in 0..=255 { + assert_eq!(array.at(i, context)?, JsValue::new(i)); + } + + context.register_global_property( + "myUint8Array", + array, + Attribute::WRITABLE | Attribute::ENUMERABLE | Attribute::CONFIGURABLE, + ); + + Ok(()) +}