use crate::{
FromJSValue, HostError, IntoJSValue, JSContext, JSObject, JSObjectOps, JSResult, JSTypeOf,
JSValue, JSValueImpl, JSValueMapper,
};
use std::fmt;
use std::marker::PhantomData;
use std::ops::Deref;
pub struct JSArray<V: JSValueImpl>(JSObject<V>);
impl<V: JSValueImpl> Deref for JSArray<V> {
type Target = JSObject<V>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<V: JSValueImpl> Clone for JSArray<V> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<V> IntoJSValue<V> for JSArray<V>
where
V: JSValueImpl,
{
fn into_js_value(self, _ctx: &JSContext<V::Context>) -> JSValue<V> {
self.0.into_js_value()
}
}
impl<V> FromJSValue<V> for JSArray<V>
where
V: JSTypeOf,
{
fn from_js_value(ctx: &JSContext<V::Context>, value: JSValue<V>) -> JSResult<Self> {
if value.is_array() {
JSObject::from_js_value(ctx, value).map(Self)
} else {
Err(HostError::not_array().into())
}
}
}
pub trait JSArrayOps: JSValueImpl {
fn new_array(ctx: &Self::Context) -> Self;
fn get_index(&self, index: u32) -> Self;
fn set_index(&self, index: u32, value: Self) -> Self;
}
impl<V> JSArray<V>
where
V: JSObjectOps + JSArrayOps,
{
pub fn new(ctx: &JSContext<V::Context>) -> JSResult<Self> {
let value = V::new_array(ctx.as_ref());
value.try_map(|value| Self::from_js_value(ctx, JSValue::from_raw(ctx, value)))?
}
pub fn len(&self) -> JSResult<u32> {
self.0.get::<_, u32>("length")
}
pub fn is_empty(&self) -> JSResult<bool> {
self.len().map(|len| len == 0)
}
pub fn get_value(&self, index: u32) -> JSResult<JSValue<V>> {
let ctx = self.context();
self.as_value()
.get_index(index)
.try_map(|value| JSValue::from_raw(&ctx, value))
}
pub fn set_value(&self, index: u32, value: JSValue<V>) -> JSResult<()> {
self.as_value()
.set_index(index, value.into_value())
.try_map(|_| ())
}
pub fn get_opt<T>(&self, index: u32) -> JSResult<Option<T>>
where
T: FromJSValue<V>,
{
if !self.has_index(index)? {
return Ok(None);
}
let ctx = self.context();
let value = self.get_value(index)?;
T::from_js_value(&ctx, value).map(Some)
}
pub fn set<T>(&self, index: u32, value: T) -> JSResult<()>
where
T: IntoJSValue<V>,
{
let ctx = self.context();
self.set_value(index, value.into_js_value(&ctx))
}
pub fn delete(&self, index: u32) -> JSResult<bool> {
self.0.delete(index)
}
pub fn has_index(&self, index: u32) -> JSResult<bool> {
self.0.has_property(index)
}
pub fn push_value(&self, value: JSValue<V>) -> JSResult<u32> {
let index = self.len()?;
self.set_value(index, value)?;
Ok(index + 1)
}
pub fn push<T>(&self, value: T) -> JSResult<u32>
where
T: IntoJSValue<V>,
{
let ctx = self.context();
self.push_value(value.into_js_value(&ctx))
}
pub fn pop_value(&self) -> JSResult<JSValue<V>> {
let len = self.len()?;
let ctx = self.context();
if len == 0 {
return Ok(JSValue::undefined(&ctx));
}
let index = len - 1;
let value = self.get_value(index)?;
self.delete(index)?;
self.0.set("length", index)?;
Ok(value)
}
pub fn pop_opt<T>(&self) -> JSResult<Option<T>>
where
T: FromJSValue<V>,
{
if self.is_empty()? {
return Ok(None);
}
let ctx = self.context();
let value = self.pop_value()?;
T::from_js_value(&ctx, value).map(Some)
}
pub fn iter<T>(&self) -> JSResult<ArrayIter<V, T>>
where
T: FromJSValue<V>,
{
Ok(ArrayIter {
array: self.clone(),
index: 0,
count: self.len()?,
marker: PhantomData,
})
}
pub fn iter_values(&self) -> JSResult<ArrayValueIter<V>> {
Ok(ArrayValueIter {
array: self.clone(),
index: 0,
count: self.len()?,
})
}
pub fn iter_present<T>(&self) -> JSResult<ArrayPresentIter<V, T>>
where
T: FromJSValue<V>,
{
Ok(ArrayPresentIter {
array: self.clone(),
index: 0,
count: self.len()?,
marker: PhantomData,
})
}
pub fn from_object(obj: JSObject<V>) -> Option<Self> {
if obj.as_value().is_array() {
Some(Self(obj))
} else {
None
}
}
}
pub struct ArrayIter<V, T>
where
V: JSObjectOps + JSArrayOps,
T: FromJSValue<V>,
{
array: JSArray<V>,
index: u32,
count: u32,
marker: PhantomData<T>,
}
impl<V, T> Iterator for ArrayIter<V, T>
where
V: JSObjectOps + JSArrayOps,
T: FromJSValue<V>,
{
type Item = JSResult<T>;
fn next(&mut self) -> Option<Self::Item> {
if self.index < self.count {
let ctx = self.array.context();
let result = self
.array
.get_value(self.index)
.and_then(|value| T::from_js_value(&ctx, value));
self.index += 1;
Some(result)
} else {
None
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let len = self.len();
(len, Some(len))
}
}
impl<V, T> ExactSizeIterator for ArrayIter<V, T>
where
V: JSObjectOps + JSArrayOps,
T: FromJSValue<V>,
{
fn len(&self) -> usize {
(self.count - self.index) as usize
}
}
pub struct ArrayValueIter<V>
where
V: JSObjectOps + JSArrayOps,
{
array: JSArray<V>,
index: u32,
count: u32,
}
impl<V> Iterator for ArrayValueIter<V>
where
V: JSObjectOps + JSArrayOps,
{
type Item = JSResult<JSValue<V>>;
fn next(&mut self) -> Option<Self::Item> {
if self.index < self.count {
let result = self.array.get_value(self.index);
self.index += 1;
Some(result)
} else {
None
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let len = self.len();
(len, Some(len))
}
}
impl<V> ExactSizeIterator for ArrayValueIter<V>
where
V: JSObjectOps + JSArrayOps,
{
fn len(&self) -> usize {
(self.count - self.index) as usize
}
}
pub struct ArrayPresentIter<V, T>
where
V: JSObjectOps + JSArrayOps,
T: FromJSValue<V>,
{
array: JSArray<V>,
index: u32,
count: u32,
marker: PhantomData<T>,
}
impl<V, T> Iterator for ArrayPresentIter<V, T>
where
V: JSObjectOps + JSArrayOps,
T: FromJSValue<V>,
{
type Item = JSResult<T>;
fn next(&mut self) -> Option<Self::Item> {
while self.index < self.count {
let index = self.index;
self.index += 1;
let has_index = match self.array.has_index(index) {
Ok(has_index) => has_index,
Err(err) => return Some(Err(err)),
};
if has_index {
let ctx = self.array.context();
let value = match self.array.get_value(index) {
Ok(value) => value,
Err(err) => return Some(Err(err)),
};
return Some(T::from_js_value(&ctx, value));
}
}
None
}
}
impl<V, T> IntoJSValue<V> for Vec<T>
where
V: JSObjectOps + JSArrayOps,
T: IntoJSValue<V>,
{
fn into_js_value(self, ctx: &JSContext<V::Context>) -> JSValue<V> {
let array = JSArray::new(ctx).unwrap();
for item in self {
array.push(item).expect("Failed to push value into array");
}
<JSArray<V> as IntoJSValue<V>>::into_js_value(array, ctx)
}
}
impl<V, T> FromJSValue<V> for Vec<T>
where
V: JSTypeOf,
V: JSObjectOps + JSArrayOps,
T: FromJSValue<V>,
{
fn from_js_value(ctx: &JSContext<V::Context>, value: JSValue<V>) -> JSResult<Self> {
if value.is_array() {
let array = JSArray::from_js_value(ctx, value)?;
array.iter::<T>()?.collect::<JSResult<Vec<_>>>()
} else {
Err(HostError::not_array().into())
}
}
}
impl<V: JSValueImpl> crate::function::JSParameterType for JSArray<V> {}
impl<V> fmt::Display for JSArray<V>
where
V: JSTypeOf + crate::JSValueConversion,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.deref().fmt(f)
}
}
impl<V> fmt::Debug for JSArray<V>
where
V: JSTypeOf + crate::JSValueConversion,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "JSArray({})", self)
}
}