1use crate::safe_ref::Mut;
4use crate::{
5 atom::PredefinedAtom,
6 function::{MutFn, This},
7 Ctx, Error, FromJs, Function, IntoJs, Object, Result, Value,
8};
9use core::{iter::FusedIterator, marker::PhantomData};
10
11pub struct Iterable<I>(pub I);
35
36impl<I> From<I> for Iterable<I> {
37 fn from(iter: I) -> Self {
38 Iterable(iter)
39 }
40}
41
42impl<'js, I, T> IntoJs<'js> for Iterable<I>
43where
44 I: IntoIterator<Item = T> + 'js,
45 I::IntoIter: 'js,
46 T: IntoJs<'js> + 'js,
47{
48 fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
49 let iter = Mut::new(Some(self.0.into_iter()));
50
51 let iterator_fn = Function::new(
52 ctx.clone(),
53 MutFn::new(move |ctx: Ctx<'js>| -> Result<Object<'js>> {
54 let iter_obj = Object::new(ctx.clone())?;
55 let iter_taken = iter.lock().take();
56
57 let state = Mut::new(iter_taken);
58 let next_fn = Function::new(
59 ctx.clone(),
60 MutFn::new(move |ctx: Ctx<'js>| -> Result<Object<'js>> {
61 let result = Object::new(ctx.clone())?;
62 let mut state_ref = state.lock();
63
64 if let Some(ref mut it) = *state_ref {
65 if let Some(value) = it.next() {
66 result.set(PredefinedAtom::Value, value.into_js(&ctx)?)?;
67 result.set(PredefinedAtom::Done, false)?;
68 } else {
69 result.set(PredefinedAtom::Done, true)?;
70 *state_ref = None;
71 }
72 } else {
73 result.set(PredefinedAtom::Done, true)?;
74 }
75 Ok(result)
76 }),
77 )?;
78
79 iter_obj.set(PredefinedAtom::Next, next_fn)?;
80 Ok(iter_obj)
81 }),
82 )?;
83
84 let obj = Object::new(ctx.clone())?;
85 obj.set(PredefinedAtom::SymbolIterator, iterator_fn)?;
86 Ok(obj.into_value())
87 }
88}
89
90pub struct JsIterator<'js, T = Value<'js>> {
118 iterator: Object<'js>,
119 done: bool,
120 _marker: PhantomData<T>,
121}
122
123impl<'js, T> JsIterator<'js, T> {
124 pub fn into_inner(self) -> Object<'js> {
126 self.iterator
127 }
128
129 pub fn typed<U: FromJs<'js>>(self) -> JsIterator<'js, U> {
134 JsIterator {
135 iterator: self.iterator,
136 done: self.done,
137 _marker: PhantomData,
138 }
139 }
140}
141
142impl<'js, T: FromJs<'js>> Iterator for JsIterator<'js, T> {
143 type Item = Result<T>;
144
145 fn next(&mut self) -> Option<Self::Item> {
146 if self.done {
147 return None;
148 }
149
150 let next_fn: Function<'js> = match self.iterator.get(PredefinedAtom::Next) {
151 Ok(f) => f,
152 Err(e) => return Some(Err(e)),
153 };
154
155 let result: Object<'js> = match next_fn.call((This(self.iterator.clone()),)) {
156 Ok(r) => r,
157 Err(e) => return Some(Err(e)),
158 };
159
160 let done: bool = match result.get(PredefinedAtom::Done) {
161 Ok(d) => d,
162 Err(e) => return Some(Err(e)),
163 };
164
165 if done {
166 self.done = true;
167 return None;
168 }
169
170 let value: Value<'js> = match result.get(PredefinedAtom::Value) {
171 Ok(v) => v,
172 Err(e) => return Some(Err(e)),
173 };
174
175 Some(T::from_js(self.iterator.ctx(), value))
176 }
177}
178
179impl<'js, T: FromJs<'js>> FusedIterator for JsIterator<'js, T> {}
180
181impl<'js, T: FromJs<'js>> FromJs<'js> for JsIterator<'js, T> {
182 fn from_js(_ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
183 let obj = Object::from_value(value)?;
184
185 if let Ok(iter_fn) = obj.get::<_, Function<'js>>(PredefinedAtom::SymbolIterator) {
187 let iterator: Object<'js> = iter_fn.call((This(obj),))?;
188 return Ok(JsIterator {
189 iterator,
190 done: false,
191 _marker: PhantomData,
192 });
193 }
194
195 if obj.contains_key(PredefinedAtom::Next)? {
197 return Ok(JsIterator {
198 iterator: obj,
199 done: false,
200 _marker: PhantomData,
201 });
202 }
203
204 Err(Error::new_from_js(
205 "value",
206 "iterable (object with Symbol.iterator or next)",
207 ))
208 }
209}
210
211#[cfg(test)]
212mod test {
213 use super::*;
214 use crate::*;
215
216 #[test]
217 fn iterable_spread() {
218 test_with(|ctx| {
219 let iter = Iterable::from(vec![1i32, 2, 3]);
220 ctx.globals().set("myIter", iter).unwrap();
221 let result: Vec<i32> = ctx.eval("[...myIter]").unwrap();
222 assert_eq!(result, vec![1, 2, 3]);
223 });
224 }
225
226 #[test]
227 fn iterable_for_of() {
228 test_with(|ctx| {
229 let iter = Iterable::from(vec!["a", "b", "c"]);
230 ctx.globals().set("myIter", iter).unwrap();
231 let result: alloc::string::String = ctx
232 .eval(
233 r#"
234 let s = "";
235 for (const x of myIter) { s += x; }
236 s
237 "#,
238 )
239 .unwrap();
240 assert_eq!(result, "abc");
241 });
242 }
243
244 #[test]
245 fn iterable_from_range() {
246 test_with(|ctx| {
247 let iter = Iterable::from(0..5);
248 ctx.globals().set("myIter", iter).unwrap();
249 let result: Vec<i32> = ctx.eval("[...myIter]").unwrap();
250 assert_eq!(result, vec![0, 1, 2, 3, 4]);
251 });
252 }
253
254 #[test]
255 fn iterable_single_use() {
256 test_with(|ctx| {
257 let iter = Iterable::from(vec![1i32, 2]);
258 ctx.globals().set("myIter", iter).unwrap();
259 let first: Vec<i32> = ctx.eval("[...myIter]").unwrap();
261 assert_eq!(first, vec![1, 2]);
262 let second: Vec<i32> = ctx.eval("[...myIter]").unwrap();
264 assert_eq!(second, Vec::<i32>::new());
265 });
266 }
267
268 #[test]
269 fn js_iter_from_array() {
270 test_with(|ctx| {
271 let iter: JsIterator<i32> = ctx.eval("[1, 2, 3][Symbol.iterator]()").unwrap();
272 let values: Vec<i32> = iter.filter_map(|r| r.ok()).collect();
273 assert_eq!(values, vec![1, 2, 3]);
274 });
275 }
276
277 #[test]
278 fn js_iter_from_iterable() {
279 test_with(|ctx| {
280 let iter: JsIterator<i32> = ctx.eval("[4, 5, 6]").unwrap();
282 let values: Vec<i32> = iter.filter_map(|r| r.ok()).collect();
283 assert_eq!(values, vec![4, 5, 6]);
284 });
285 }
286
287 #[test]
288 fn js_iter_from_generator() {
289 test_with(|ctx| {
290 let iter: JsIterator<i32> = ctx
291 .eval(
292 r#"
293 (function*() {
294 yield 10;
295 yield 20;
296 yield 30;
297 })()
298 "#,
299 )
300 .unwrap();
301 let values: Vec<i32> = iter.filter_map(|r| r.ok()).collect();
302 assert_eq!(values, vec![10, 20, 30]);
303 });
304 }
305
306 #[test]
307 fn js_iter_roundtrip() {
308 test_with(|ctx| {
309 let rust_iter = Iterable::from(vec![100i32, 200, 300]);
311 ctx.globals().set("myIter", rust_iter).unwrap();
312 let js_iter: JsIterator<i32> = ctx.eval("myIter").unwrap();
313 let values: Vec<i32> = js_iter.filter_map(|r| r.ok()).collect();
314 assert_eq!(values, vec![100, 200, 300]);
315 });
316 }
317
318 #[test]
319 fn js_iter_raw_values() {
320 test_with(|ctx| {
321 let iter: JsIterator<Value> = ctx.eval("[1, 'two', 3]").unwrap();
323 let values: Vec<Value> = iter.filter_map(|r| r.ok()).collect();
324 assert_eq!(values.len(), 3);
325 assert!(values[0].is_int());
326 assert!(values[1].is_string());
327 assert!(values[2].is_int());
328 });
329 }
330
331 #[test]
332 fn js_iter_typed_conversion() {
333 test_with(|ctx| {
334 let iter: JsIterator<Value> = ctx.eval("[1, 2, 3]").unwrap();
336 let typed = iter.typed::<i32>();
337 let values: Vec<i32> = typed.filter_map(|r| r.ok()).collect();
338 assert_eq!(values, vec![1, 2, 3]);
339 });
340 }
341
342 #[test]
343 fn js_iter_strings() {
344 test_with(|ctx| {
345 let iter: JsIterator<alloc::string::String> =
346 ctx.eval("['hello', 'world', 'rust']").unwrap();
347 let values: Vec<alloc::string::String> = iter.filter_map(|r| r.ok()).collect();
348 assert_eq!(values, vec!["hello", "world", "rust"]);
349 });
350 }
351
352 #[test]
353 fn js_iter_floats() {
354 test_with(|ctx| {
355 let iter: JsIterator<f64> = ctx.eval("[1.5, 2.7, 3.54]").unwrap();
356 let values: Vec<f64> = iter.filter_map(|r| r.ok()).collect();
357 assert_eq!(values, vec![1.5, 2.7, 3.54]);
358 });
359 }
360
361 #[test]
362 fn js_iter_bools() {
363 test_with(|ctx| {
364 let iter: JsIterator<bool> = ctx.eval("[true, false, true]").unwrap();
365 let values: Vec<bool> = iter.filter_map(|r| r.ok()).collect();
366 assert_eq!(values, vec![true, false, true]);
367 });
368 }
369
370 #[test]
371 fn js_iter_objects() {
372 test_with(|ctx| {
373 let iter: JsIterator<Object> = ctx.eval("[{a: 1}, {b: 2}]").unwrap();
374 let objects: Vec<Object> = iter.filter_map(|r| r.ok()).collect();
375 assert_eq!(objects.len(), 2);
376 assert_eq!(objects[0].get::<_, i32>("a").unwrap(), 1);
377 assert_eq!(objects[1].get::<_, i32>("b").unwrap(), 2);
378 });
379 }
380
381 #[test]
382 fn js_iter_map_entries() {
383 test_with(|ctx| {
384 let iter: JsIterator<Array> =
386 ctx.eval("new Map([['a', 1], ['b', 2]]).entries()").unwrap();
387 let entries: Vec<Array> = iter.filter_map(|r| r.ok()).collect();
388 assert_eq!(entries.len(), 2);
389 assert_eq!(entries[0].get::<alloc::string::String>(0).unwrap(), "a");
390 assert_eq!(entries[0].get::<i32>(1).unwrap(), 1);
391 });
392 }
393
394 #[test]
395 fn js_iter_set() {
396 test_with(|ctx| {
397 let iter: JsIterator<i32> = ctx.eval("new Set([1, 2, 3])").unwrap();
398 let values: Vec<i32> = iter.filter_map(|r| r.ok()).collect();
399 assert_eq!(values, vec![1, 2, 3]);
400 });
401 }
402}