rquickjs_core/value/
array.rs

1//! JavaScript array types.
2
3use crate::{atom::PredefinedAtom, qjs, Ctx, FromJs, IntoJs, Object, Result, Value};
4use std::{iter::FusedIterator, marker::PhantomData};
5
6use super::convert::FromIteratorJs;
7
8/// Rust representation of a JavaScript object optimized as an array.
9///
10/// JavaScript array's are objects and can be used as such.
11/// However arrays in QuickJS are optimized when they do not have any holes.
12/// This value represents such an optimized array.
13#[derive(Debug, PartialEq, Clone, Eq, Hash)]
14#[repr(transparent)]
15pub struct Array<'js>(pub(crate) Object<'js>);
16
17impl<'js> Array<'js> {
18    /// Create a new JavaScript array.
19    pub fn new(ctx: Ctx<'js>) -> Result<Self> {
20        Ok(Array(unsafe {
21            let val = qjs::JS_NewArray(ctx.as_ptr());
22            ctx.handle_exception(val)?;
23            Value::from_js_value(ctx, val)
24                .into_object()
25                .expect("arrays should always be objects")
26        }))
27    }
28
29    /// Get the length of the JavaScript array.
30    pub fn len(&self) -> usize {
31        let ctx = self.ctx();
32        let value = self.0.as_js_value();
33        unsafe {
34            let val = qjs::JS_GetProperty(ctx.as_ptr(), value, PredefinedAtom::Length as _);
35            assert!(qjs::JS_IsInt(val));
36            qjs::JS_VALUE_GET_INT(val) as _
37        }
38    }
39
40    /// Returns whether a JavaScript array is empty.
41    pub fn is_empty(&self) -> bool {
42        self.len() == 0
43    }
44
45    /// Get the value at an index in the JavaScript array.
46    pub fn get<V: FromJs<'js>>(&self, idx: usize) -> Result<V> {
47        let ctx = self.ctx();
48        let obj = self.0.as_js_value();
49        let val = unsafe {
50            let val = qjs::JS_GetPropertyUint32(ctx.as_ptr(), obj, idx as _);
51            let val = ctx.handle_exception(val)?;
52            Value::from_js_value(ctx.clone(), val)
53        };
54        V::from_js(ctx, val)
55    }
56
57    /// Set the value at an index in the JavaScript array.
58    pub fn set<V: IntoJs<'js>>(&self, idx: usize, val: V) -> Result<()> {
59        let ctx = self.ctx();
60        let obj = self.0.as_js_value();
61        let val = val.into_js(ctx)?.into_js_value();
62        unsafe {
63            if 0 > qjs::JS_SetPropertyUint32(ctx.as_ptr(), obj, idx as _, val) {
64                return Err(ctx.raise_exception());
65            }
66        }
67        Ok(())
68    }
69
70    /// Get an iterator over elements of an array
71    pub fn iter<T: FromJs<'js>>(&self) -> ArrayIter<'js, T> {
72        let count = self.len() as _;
73        ArrayIter {
74            array: self.clone(),
75            index: 0,
76            count,
77            marker: PhantomData,
78        }
79    }
80
81    pub fn into_object(self) -> Object<'js> {
82        self.0
83    }
84
85    pub fn as_object(&self) -> &Object<'js> {
86        &self.0
87    }
88}
89
90/// The iterator for an array
91pub struct ArrayIter<'js, T> {
92    array: Array<'js>,
93    index: u32,
94    count: u32,
95    marker: PhantomData<T>,
96}
97
98impl<'js, T> Iterator for ArrayIter<'js, T>
99where
100    T: FromJs<'js>,
101{
102    type Item = Result<T>;
103
104    fn next(&mut self) -> Option<Self::Item> {
105        if self.index < self.count {
106            let res = self.array.get(self.index as _);
107            self.index += 1;
108            Some(res)
109        } else {
110            None
111        }
112    }
113
114    fn size_hint(&self) -> (usize, Option<usize>) {
115        let len = self.len();
116        (len, Some(len))
117    }
118}
119
120impl<'js, T> DoubleEndedIterator for ArrayIter<'js, T>
121where
122    T: FromJs<'js>,
123{
124    fn next_back(&mut self) -> Option<Self::Item> {
125        if self.index < self.count {
126            self.count -= 1;
127            let res = self.array.get(self.count as _);
128            Some(res)
129        } else {
130            None
131        }
132    }
133}
134
135impl<'js, T> ExactSizeIterator for ArrayIter<'js, T>
136where
137    T: FromJs<'js>,
138{
139    fn len(&self) -> usize {
140        (self.count - self.index) as _
141    }
142}
143
144impl<'js, T> FusedIterator for ArrayIter<'js, T> where T: FromJs<'js> {}
145
146impl<'js> IntoIterator for Array<'js> {
147    type Item = Result<Value<'js>>;
148    type IntoIter = ArrayIter<'js, Value<'js>>;
149
150    fn into_iter(self) -> Self::IntoIter {
151        let count = self.len() as _;
152        ArrayIter {
153            array: self,
154            index: 0,
155            count,
156            marker: PhantomData,
157        }
158    }
159}
160
161impl<'js, A> FromIteratorJs<'js, A> for Array<'js>
162where
163    A: IntoJs<'js>,
164{
165    type Item = Value<'js>;
166
167    fn from_iter_js<T>(ctx: &Ctx<'js>, iter: T) -> Result<Self>
168    where
169        T: IntoIterator<Item = A>,
170    {
171        let array = Array::new(ctx.clone())?;
172        for (idx, item) in iter.into_iter().enumerate() {
173            let item = item.into_js(ctx)?;
174            array.set(idx as _, item)?;
175        }
176        Ok(array)
177    }
178}
179
180#[cfg(test)]
181mod test {
182
183    use crate::*;
184    #[test]
185    fn from_javascript() {
186        test_with(|ctx| {
187            let val: Array = ctx
188                .eval(
189                    r#"
190                let a = [1,2,3,4,10,"b"]
191                a[6] = {}
192                a[10] = () => {"hallo"};
193                a
194                "#,
195                )
196                .unwrap();
197            assert_eq!(val.len(), 11);
198            assert_eq!(val.get::<i32>(3).unwrap(), 4);
199            assert_eq!(val.get::<i32>(4).unwrap(), 10);
200            let _six: Object = val.get(6).unwrap();
201        });
202    }
203
204    #[test]
205    fn into_object() {
206        test_with(|ctx| {
207            let val: Array = ctx
208                .eval(
209                    r#"
210                let a = [1,2,3];
211                a
212            "#,
213                )
214                .unwrap();
215            let object = val.into_object();
216            assert_eq!(object.get::<_, i32>(0).unwrap(), 1);
217        })
218    }
219
220    #[test]
221    fn into_iter() {
222        test_with(|ctx| {
223            let val: Array = ctx
224                .eval(
225                    r#"
226                      [1,'abcd',true]
227                    "#,
228                )
229                .unwrap();
230            let elems: Vec<_> = val.into_iter().collect::<Result<_>>().unwrap();
231            assert_eq!(elems.len(), 3);
232            assert_eq!(i8::from_js(&ctx, elems[0].clone()).unwrap(), 1);
233            assert_eq!(StdString::from_js(&ctx, elems[1].clone()).unwrap(), "abcd");
234            assert!(bool::from_js(&ctx, elems[2].clone()).unwrap());
235        })
236    }
237
238    #[test]
239    fn iter() {
240        test_with(|ctx| {
241            let val: Array = ctx
242                .eval(
243                    r#"
244                      ["a", 'b', '', "cdef"]
245                    "#,
246                )
247                .unwrap();
248            let elems: Vec<StdString> = val.iter().collect::<Result<_>>().unwrap();
249            assert_eq!(elems.len(), 4);
250            assert_eq!(elems[0], "a");
251            assert_eq!(elems[1], "b");
252            assert_eq!(elems[2], "");
253            assert_eq!(elems[3], "cdef");
254        })
255    }
256
257    #[test]
258    fn collect_js() {
259        test_with(|ctx| {
260            let array = [1i32, 2, 3]
261                .iter()
262                .cloned()
263                .collect_js::<Array>(&ctx)
264                .unwrap();
265            assert_eq!(array.len(), 3);
266            assert_eq!(i32::from_js(&ctx, array.get(0).unwrap()).unwrap(), 1);
267            assert_eq!(i32::from_js(&ctx, array.get(1).unwrap()).unwrap(), 2);
268            assert_eq!(i32::from_js(&ctx, array.get(2).unwrap()).unwrap(), 3);
269        })
270    }
271}