use crate::rong::spawn;
use crate::{IntoJSValue, JSContext, JSFunc, JSObject, JSObjectOps, JSResult, JSSymbol, JSValue};
use futures::{Stream, StreamExt};
use std::cell::RefCell;
use std::pin::Pin;
use std::rc::Rc;
use tokio::sync::Mutex;
pub struct JSIterator<V, T>
where
V: JSObjectOps + 'static,
T: IntoJSValue<V> + 'static,
{
inner: Rc<RefCell<Box<dyn Iterator<Item = T> + 'static>>>,
result: JSObject<V>,
}
impl<V, T> JSIterator<V, T>
where
V: JSObjectOps + 'static,
T: IntoJSValue<V> + 'static,
{
pub fn from<I>(iterable: I, ctx: &JSContext<V::Context>) -> Self
where
I: IntoIterator<Item = T> + 'static,
I::IntoIter: 'static,
{
Self {
inner: Rc::new(RefCell::new(Box::new(iterable.into_iter()))),
result: JSObject::new(ctx),
}
}
pub fn next(&self, ctx: &JSContext<V::Context>) -> JSResult<JSObject<V>> {
let result = self.result.clone();
let mut iter = self.inner.borrow_mut();
match iter.next() {
Some(item) => {
result.set("done", false)?;
let value = <T as IntoJSValue<V>>::into_js_value(item, ctx);
result.set("value", value)?;
}
None => {
result.set("done", true)?;
result.set("value", JSValue::undefined(ctx))?;
}
}
Ok(result)
}
pub fn to_js_iterable(&self, ctx: &JSContext<V::Context>) -> JSResult<JSObject<V>> {
let iterable = JSObject::new(ctx);
let iterator_obj = JSObject::new(ctx);
let iterator_instance = self.clone();
let next_fn = JSFunc::new(ctx, move |ctx: JSContext<V::Context>| -> JSObject<V> {
iterator_instance.next(&ctx).unwrap_or_else(|_| {
let result = JSObject::new(&ctx);
result.set("done", true).ok();
result.set("value", JSValue::undefined(&ctx)).ok();
result
})
})?;
iterator_obj.set("next", next_fn)?;
let iter_obj_clone = iterator_obj.clone();
let iterator_fn = JSFunc::new(ctx, move || iter_obj_clone.clone())?;
let symbol = ctx
.global()
.get::<_, JSObject<V>>("Symbol")?
.get::<_, JSSymbol<V>>("iterator")?;
iterable.set(symbol, iterator_fn)?;
Ok(iterable)
}
}
impl<V, T> Clone for JSIterator<V, T>
where
V: JSObjectOps + 'static,
T: IntoJSValue<V> + 'static,
{
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
result: self.result.clone(),
}
}
}
pub struct JSAsyncIterator<V, T>
where
V: JSObjectOps + 'static,
T: IntoJSValue<V> + 'static,
{
stream: Rc<Mutex<Pin<Box<dyn Stream<Item = T> + 'static>>>>,
result: JSObject<V>,
}
impl<V, T> JSAsyncIterator<V, T>
where
V: JSObjectOps + 'static,
T: IntoJSValue<V> + 'static,
{
pub fn from<S>(stream: S, ctx: &JSContext<V::Context>) -> Self
where
S: Stream<Item = T> + 'static,
{
Self {
stream: Rc::new(Mutex::new(Box::pin(stream))),
result: JSObject::new(ctx),
}
}
pub async fn next(&self, ctx: &JSContext<V::Context>) -> JSResult<JSObject<V>> {
let result = self.result.clone();
let mut stream = self.stream.lock().await;
match stream.next().await {
Some(item) => {
result.set("done", false)?;
let value = <T as IntoJSValue<V>>::into_js_value(item, ctx);
result.set("value", value)?;
}
None => {
result.set("done", true)?;
result.set("value", JSValue::undefined(ctx))?;
}
}
Ok(result)
}
pub fn to_js_async_iterable(&self, ctx: &JSContext<V::Context>) -> JSResult<JSObject<V>> {
let iterable = JSObject::new(ctx);
let iterator_obj = JSObject::new(ctx);
let iterator_instance = self.clone();
let next_fn = JSFunc::new(ctx, move |ctx: JSContext<V::Context>| -> JSObject<V> {
match ctx.promise() {
Ok((promise, resolve, _reject)) => {
let iterator_clone = iterator_instance.clone();
spawn(async move {
match iterator_clone.next(&ctx).await {
Ok(result) => {
let _ = resolve.call::<_, ()>(None, (result,));
}
Err(_) => {
let result = JSObject::new(&ctx);
result.set("done", true).ok();
result.set("value", JSValue::undefined(&ctx)).ok();
let _ = resolve.call::<_, ()>(None, (result,));
}
}
});
promise.into_object()
}
Err(_) => {
let result = JSObject::new(&ctx);
result.set("done", true).ok();
result.set("value", JSValue::undefined(&ctx)).ok();
result
}
}
})?;
iterator_obj.set("next", next_fn)?;
let iter_obj_clone = iterator_obj.clone();
let iterator_fn = JSFunc::new(ctx, move |_ctx: JSContext<V::Context>| -> JSObject<V> {
iter_obj_clone.clone()
})?;
let symbol = ctx
.global()
.get::<_, JSObject<V>>("Symbol")?
.get::<_, JSSymbol<V>>("asyncIterator")?;
iterable.set(symbol, iterator_fn)?;
Ok(iterable)
}
}
impl<V, T> Clone for JSAsyncIterator<V, T>
where
V: JSObjectOps + 'static,
T: IntoJSValue<V> + 'static,
{
fn clone(&self) -> Self {
Self {
stream: self.stream.clone(),
result: self.result.clone(),
}
}
}
pub trait IntoJSIteratorExt<V, T>
where
V: JSObjectOps + 'static,
{
fn to_js_iter(self, ctx: &JSContext<V::Context>) -> JSResult<JSObject<V>>;
}
impl<V, T, I> IntoJSIteratorExt<V, T> for I
where
V: JSObjectOps + 'static,
T: IntoJSValue<V> + 'static,
I: IntoIterator<Item = T> + 'static,
I::IntoIter: 'static,
{
fn to_js_iter(self, ctx: &JSContext<V::Context>) -> JSResult<JSObject<V>> {
let js_iter = JSIterator::from(self, ctx);
js_iter.to_js_iterable(ctx)
}
}
pub trait IntoJSAsyncIteratorExt<V, T>
where
V: JSObjectOps + 'static,
{
fn to_js_async_iter(self, ctx: &JSContext<V::Context>) -> JSResult<JSObject<V>>;
}
impl<V, T, S> IntoJSAsyncIteratorExt<V, T> for S
where
V: JSObjectOps + 'static,
T: IntoJSValue<V> + 'static,
S: Stream<Item = T> + 'static,
{
fn to_js_async_iter(self, ctx: &JSContext<V::Context>) -> JSResult<JSObject<V>> {
let js_iter = JSAsyncIterator::from(self, ctx);
js_iter.to_js_async_iterable(ctx)
}
}