use crate::js_lifetime::JsLifetime;
use crate::object::Property;
use crate::safe_ref::Mut;
use crate::{
atom::PredefinedAtom,
function::{MutFn, This},
Ctx, Error, FromJs, Function, IntoJs, Object, Result, Value,
};
use core::{iter::FusedIterator, marker::PhantomData};
struct IteratorPrototypeCache<'js>(Object<'js>);
unsafe impl<'js> JsLifetime<'js> for IteratorPrototypeCache<'js> {
type Changed<'to> = IteratorPrototypeCache<'to>;
}
fn get_iterator_prototype<'js>(ctx: &Ctx<'js>) -> Result<Object<'js>> {
if let Some(guard) = ctx.userdata::<IteratorPrototypeCache>() {
return Ok(guard.0.clone());
}
let iterator_ctor: Object = ctx.globals().get(PredefinedAtom::Iterator)?;
let proto: Object = iterator_ctor.get(PredefinedAtom::Prototype)?;
let _ = ctx.store_userdata(IteratorPrototypeCache(proto.clone()));
Ok(proto)
}
fn build_iterator_object<'js, F>(ctx: &Ctx<'js>, next_fn: F) -> Result<Object<'js>>
where
F: FnMut(&Ctx<'js>) -> Option<Result<Value<'js>>> + 'js,
{
let iter_proto = get_iterator_prototype(ctx)?;
let proto = Object::new(ctx.clone())?;
proto.set_prototype(Some(&iter_proto))?;
let state = Mut::new(next_fn);
let next = Function::new(
ctx.clone(),
MutFn::new(move |ctx: Ctx<'js>| -> Result<Object<'js>> {
let result = Object::new(ctx.clone())?;
match (state.lock())(&ctx) {
Some(Ok(value)) => {
result.set(PredefinedAtom::Value, value)?;
result.set(PredefinedAtom::Done, false)?;
}
Some(Err(e)) => return Err(e),
None => {
result.set(PredefinedAtom::Done, true)?;
}
}
Ok(result)
}),
)?;
proto.prop(
PredefinedAtom::Next,
Property::from(next).enumerable().writable().configurable(),
)?;
let iter_obj = Object::new(ctx.clone())?;
iter_obj.set_prototype(Some(&proto))?;
Ok(iter_obj)
}
pub struct Iterable<F>(pub F);
impl<F> Iterable<ClosureWrapper<F>> {
pub fn from_fn(f: F) -> Self {
Iterable(ClosureWrapper(f))
}
}
impl<I: IntoIterator> From<I> for Iterable<IteratorWrapper<I::IntoIter>> {
fn from(iter: I) -> Self {
Iterable(IteratorWrapper(Some(iter.into_iter())))
}
}
pub trait IterableFn {
type Item;
fn call(&mut self) -> Option<Self::Item>;
}
pub struct ClosureWrapper<F>(F);
impl<T, F: FnMut() -> Option<T>> IterableFn for ClosureWrapper<F> {
type Item = T;
fn call(&mut self) -> Option<T> {
(self.0)()
}
}
pub struct IteratorWrapper<I>(Option<I>);
impl<I: Iterator> IterableFn for IteratorWrapper<I> {
type Item = I::Item;
fn call(&mut self) -> Option<I::Item> {
self.0.as_mut()?.next()
}
}
impl<'js, F> IntoJs<'js> for Iterable<F>
where
F: IterableFn + 'js,
F::Item: IntoJs<'js> + 'js,
{
fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
let mut f = self.0;
let iter_obj = build_iterator_object(ctx, move |ctx| f.call().map(|v| v.into_js(ctx)))?;
Ok(iter_obj.into_value())
}
}
pub struct JsIterator<'js, T = Value<'js>> {
iterator: Object<'js>,
done: bool,
_marker: PhantomData<T>,
}
impl<'js, T> JsIterator<'js, T> {
pub fn into_inner(self) -> Object<'js> {
self.iterator
}
pub fn typed<U: FromJs<'js>>(self) -> JsIterator<'js, U> {
JsIterator {
iterator: self.iterator,
done: self.done,
_marker: PhantomData,
}
}
}
impl<'js, T: FromJs<'js>> Iterator for JsIterator<'js, T> {
type Item = Result<T>;
fn next(&mut self) -> Option<Self::Item> {
if self.done {
return None;
}
let next_fn: Function<'js> = match self.iterator.get(PredefinedAtom::Next) {
Ok(f) => f,
Err(e) => return Some(Err(e)),
};
let result: Object<'js> = match next_fn.call((This(self.iterator.clone()),)) {
Ok(r) => r,
Err(e) => return Some(Err(e)),
};
let done: bool = match result.get(PredefinedAtom::Done) {
Ok(d) => d,
Err(e) => return Some(Err(e)),
};
if done {
self.done = true;
return None;
}
let value: Value<'js> = match result.get(PredefinedAtom::Value) {
Ok(v) => v,
Err(e) => return Some(Err(e)),
};
Some(T::from_js(self.iterator.ctx(), value))
}
}
impl<'js, T: FromJs<'js>> FusedIterator for JsIterator<'js, T> {}
impl<'js, T: FromJs<'js>> FromJs<'js> for JsIterator<'js, T> {
fn from_js(_ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
let obj = Object::from_value(value)?;
if let Ok(iter_fn) = obj.get::<_, Function<'js>>(PredefinedAtom::SymbolIterator) {
let iterator: Object<'js> = iter_fn.call((This(obj),))?;
return Ok(JsIterator {
iterator,
done: false,
_marker: PhantomData,
});
}
if obj.contains_key(PredefinedAtom::Next)? {
return Ok(JsIterator {
iterator: obj,
done: false,
_marker: PhantomData,
});
}
Err(Error::new_from_js(
"value",
"iterable (object with Symbol.iterator or next)",
))
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::*;
#[test]
fn from_vec() {
test_with(|ctx| {
let iter = Iterable::from(vec![1i32, 2, 3]);
ctx.globals().set("myIter", iter).unwrap();
let result: Vec<i32> = ctx.eval("[...myIter]").unwrap();
assert_eq!(result, vec![1, 2, 3]);
});
}
#[test]
fn from_range() {
test_with(|ctx| {
let iter = Iterable::from(0..5);
ctx.globals().set("myIter", iter).unwrap();
let result: Vec<i32> = ctx.eval("[...myIter]").unwrap();
assert_eq!(result, vec![0, 1, 2, 3, 4]);
});
}
#[test]
fn from_closure() {
test_with(|ctx| {
let mut i = 0i32;
let iter = Iterable::from_fn(move || {
i += 1;
if i <= 3 {
Some(i)
} else {
None
}
});
ctx.globals().set("myIter", iter).unwrap();
let result: Vec<i32> = ctx.eval("[...myIter]").unwrap();
assert_eq!(result, vec![1, 2, 3]);
});
}
#[test]
fn for_of() {
test_with(|ctx| {
let iter = Iterable::from(vec!["a", "b", "c"]);
ctx.globals().set("myIter", iter).unwrap();
let result: alloc::string::String = ctx
.eval(r#"let s = ""; for (const x of myIter) { s += x; } s"#)
.unwrap();
assert_eq!(result, "abc");
});
}
#[test]
fn symbol_iterator_returns_this() {
test_with(|ctx| {
let iter = Iterable::from(vec![1i32]);
ctx.globals().set("myIter", iter).unwrap();
let ok: bool = ctx.eval("myIter[Symbol.iterator]() === myIter").unwrap();
assert!(ok);
});
}
#[test]
fn prototype_chain() {
test_with(|ctx| {
let iter = Iterable::from(vec![1i32]);
ctx.globals().set("myIter", iter).unwrap();
let ok: bool = ctx
.eval(
r#"
const iterProto = Object.getPrototypeOf(
Object.getPrototypeOf([][Symbol.iterator]())
);
Object.getPrototypeOf(Object.getPrototypeOf(myIter)) === iterProto
"#,
)
.unwrap();
assert!(ok);
});
}
#[test]
fn next_descriptors() {
test_with(|ctx| {
let iter = Iterable::from(vec![1i32]);
ctx.globals().set("myIter", iter).unwrap();
let ok: bool = ctx
.eval(
r#"
const proto = Object.getPrototypeOf(myIter);
const desc = Object.getOwnPropertyDescriptor(proto, "next");
desc.enumerable && desc.writable && desc.configurable
"#,
)
.unwrap();
assert!(ok);
});
}
#[test]
fn js_iter_from_array() {
test_with(|ctx| {
let iter: JsIterator<i32> = ctx.eval("[1, 2, 3][Symbol.iterator]()").unwrap();
let values: Vec<i32> = iter.filter_map(|r| r.ok()).collect();
assert_eq!(values, vec![1, 2, 3]);
});
}
#[test]
fn js_iter_from_iterable() {
test_with(|ctx| {
let iter: JsIterator<i32> = ctx.eval("[4, 5, 6]").unwrap();
let values: Vec<i32> = iter.filter_map(|r| r.ok()).collect();
assert_eq!(values, vec![4, 5, 6]);
});
}
#[test]
fn js_iter_from_generator() {
test_with(|ctx| {
let iter: JsIterator<i32> = ctx
.eval("(function*() { yield 10; yield 20; yield 30; })()")
.unwrap();
let values: Vec<i32> = iter.filter_map(|r| r.ok()).collect();
assert_eq!(values, vec![10, 20, 30]);
});
}
#[test]
fn js_iter_roundtrip() {
test_with(|ctx| {
let rust_iter = Iterable::from(vec![100i32, 200, 300]);
ctx.globals().set("myIter", rust_iter).unwrap();
let js_iter: JsIterator<i32> = ctx.eval("myIter").unwrap();
let values: Vec<i32> = js_iter.filter_map(|r| r.ok()).collect();
assert_eq!(values, vec![100, 200, 300]);
});
}
#[test]
fn js_iter_raw_values() {
test_with(|ctx| {
let iter: JsIterator<Value> = ctx.eval("[1, 'two', 3]").unwrap();
let values: Vec<Value> = iter.filter_map(|r| r.ok()).collect();
assert_eq!(values.len(), 3);
});
}
#[test]
fn js_iter_typed() {
test_with(|ctx| {
let iter: JsIterator<Value> = ctx.eval("[1, 2, 3]").unwrap();
let values: Vec<i32> = iter.typed::<i32>().filter_map(|r| r.ok()).collect();
assert_eq!(values, vec![1, 2, 3]);
});
}
#[test]
fn js_iter_map_entries() {
test_with(|ctx| {
let iter: JsIterator<Array> =
ctx.eval("new Map([['a', 1], ['b', 2]]).entries()").unwrap();
let entries: Vec<Array> = iter.filter_map(|r| r.ok()).collect();
assert_eq!(entries.len(), 2);
assert_eq!(entries[0].get::<alloc::string::String>(0).unwrap(), "a");
assert_eq!(entries[0].get::<i32>(1).unwrap(), 1);
});
}
#[test]
fn js_iter_set() {
test_with(|ctx| {
let iter: JsIterator<i32> = ctx.eval("new Set([1, 2, 3])").unwrap();
let values: Vec<i32> = iter.filter_map(|r| r.ok()).collect();
assert_eq!(values, vec![1, 2, 3]);
});
}
#[test]
fn custom_iterable_fn() {
struct Counter(i32);
impl IterableFn for Counter {
type Item = i32;
fn call(&mut self) -> Option<i32> {
self.0 += 1;
(self.0 <= 3).then_some(self.0)
}
}
test_with(|ctx| {
let iter = Iterable(Counter(0));
ctx.globals().set("myIter", iter).unwrap();
let result: Vec<i32> = ctx.eval("[...myIter]").unwrap();
assert_eq!(result, vec![1, 2, 3]);
});
}
#[test]
fn custom_iterator_as_iterable_fn() {
struct Doubles(i32);
impl Iterator for Doubles {
type Item = i32;
fn next(&mut self) -> Option<i32> {
self.0 += 1;
(self.0 <= 3).then_some(self.0 * 2)
}
}
test_with(|ctx| {
let iter = Iterable::from(Doubles(0));
ctx.globals().set("myIter", iter).unwrap();
let result: Vec<i32> = ctx.eval("[...myIter]").unwrap();
assert_eq!(result, vec![2, 4, 6]);
});
}
}