1use crate::{atom::PredefinedAtom, qjs, Ctx, FromJs, IntoJs, Object, Result, Value};
4use std::{iter::FusedIterator, marker::PhantomData};
5
6use super::convert::FromIteratorJs;
7
8#[derive(Debug, PartialEq, Clone, Eq, Hash)]
14#[repr(transparent)]
15pub struct Array<'js>(pub(crate) Object<'js>);
16
17impl<'js> Array<'js> {
18 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 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 pub fn is_empty(&self) -> bool {
42 self.len() == 0
43 }
44
45 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 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 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
90pub 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}