use crate::rong::spawn_local;
use crate::{
IntoJSValue, JSArrayOps, JSContext, JSErrorFactory, JSFunc, JSObject, JSObjectOps, JSResult,
JSSymbol, JSTypeOf, JSValue, RongJSError, function::This,
};
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>>>,
ctx: JSContext<V::Context>,
}
impl<V, T> JSIterator<V, T>
where
V: JSObjectOps + 'static,
T: IntoJSValue<V> + 'static,
{
pub fn new<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()))),
ctx: ctx.clone(),
}
}
pub fn next(&self) -> JSResult<JSObject<V>> {
let result = JSObject::new(&self.ctx);
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, &self.ctx);
result.set("value", value)?;
}
None => {
result.set("done", true)?;
result.set("value", JSValue::undefined(&self.ctx))?;
}
}
Ok(result)
}
pub fn install_on(&self, ctx: &JSContext<V::Context>, obj: &JSObject<V>) -> JSResult<()> {
let iterator_instance = self.clone();
let next_fn = JSFunc::new(ctx, move |_ctx: JSContext<V::Context>| -> JSObject<V> {
iterator_instance.next().unwrap_or_else(|_| {
let result = JSObject::new(&iterator_instance.ctx);
result.set("done", true).ok();
result
.set("value", JSValue::undefined(&iterator_instance.ctx))
.ok();
result
})
})?;
obj.set("next", next_fn)?;
let inner_handle = self.inner.clone();
let return_fn = JSFunc::new(ctx, move |ctx: JSContext<V::Context>| -> JSObject<V> {
*inner_handle.borrow_mut() = Box::new(std::iter::empty());
let result = JSObject::new(&ctx);
result.set("done", true).ok();
result.set("value", JSValue::undefined(&ctx)).ok();
result
})?;
obj.set("return", return_fn)?;
let symbol = ctx
.global()
.get::<_, JSObject<V>>("Symbol")?
.get::<_, JSSymbol<V>>("iterator")?;
obj.set(
symbol,
JSFunc::new(ctx, |this: This<JSObject<V>>| this.0.clone())?,
)?;
Ok(())
}
pub fn to_js_iterable(&self, ctx: &JSContext<V::Context>) -> JSResult<JSObject<V>> {
let obj = JSObject::new(ctx);
self.install_on(ctx, &obj)?;
Ok(obj)
}
}
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(),
ctx: self.ctx.clone(),
}
}
}
pub struct JSAsyncIterator<V, T>
where
V: JSObjectOps + 'static,
T: IntoJSValue<V> + 'static,
{
stream: Rc<Mutex<Pin<Box<dyn Stream<Item = T> + 'static>>>>,
ctx: JSContext<V::Context>,
}
impl<V, T> JSAsyncIterator<V, T>
where
V: JSObjectOps + JSArrayOps + JSTypeOf + 'static,
T: IntoJSValue<V> + 'static,
V::Context: JSErrorFactory,
{
pub fn new<S>(stream: S, ctx: &JSContext<V::Context>) -> Self
where
S: Stream<Item = T> + 'static,
{
Self {
stream: Rc::new(Mutex::new(Box::pin(stream))),
ctx: ctx.clone(),
}
}
pub async fn next(&self) -> JSResult<JSObject<V>> {
let result = JSObject::new(&self.ctx);
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, &self.ctx);
if value.is_exception() {
return Err(RongJSError::from_thrown_value(value));
}
result.set("value", value)?;
}
None => {
result.set("done", true)?;
result.set("value", JSValue::undefined(&self.ctx))?;
}
}
Ok(result)
}
pub fn install_on(&self, ctx: &JSContext<V::Context>, obj: &JSObject<V>) -> JSResult<()> {
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 iter = iterator_instance.clone();
spawn_local(async move {
match iter.next().await {
Ok(result) => {
let _ = resolve.call::<_, ()>(None, (result,));
}
Err(e) => {
let err = e.into_catch_value(&ctx);
let _ = reject.call::<_, ()>(None, (err,));
}
}
});
promise.into_object()
}
Err(_) => {
let result = JSObject::new(&ctx);
result.set("done", true).ok();
result.set("value", JSValue::undefined(&ctx)).ok();
result
}
}
})?;
obj.set("next", next_fn)?;
let stream_handle = self.stream.clone();
let return_fn = JSFunc::new(ctx, move |ctx: JSContext<V::Context>| -> JSObject<V> {
let stream = stream_handle.clone();
match ctx.promise() {
Ok((promise, resolve, _reject)) => {
spawn_local(async move {
let mut guard = stream.lock().await;
*guard = Box::pin(futures::stream::empty());
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
}
}
})?;
obj.set("return", return_fn)?;
let symbol = ctx
.global()
.get::<_, JSObject<V>>("Symbol")?
.get::<_, JSSymbol<V>>("asyncIterator")?;
obj.set(
symbol,
JSFunc::new(ctx, |this: This<JSObject<V>>| this.0.clone())?,
)?;
Ok(())
}
pub fn to_js_async_iterable(&self, ctx: &JSContext<V::Context>) -> JSResult<JSObject<V>> {
let obj = JSObject::new(ctx);
self.install_on(ctx, &obj)?;
Ok(obj)
}
}
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(),
ctx: self.ctx.clone(),
}
}
}
pub trait IntoJSIteratorExt<V, T>
where
V: JSObjectOps + 'static,
{
fn to_js_iter(self, ctx: &JSContext<V::Context>) -> JSResult<JSObject<V>>;
fn install_js_iter(self, ctx: &JSContext<V::Context>, obj: &JSObject<V>) -> JSResult<()>;
}
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::new(self, ctx);
js_iter.to_js_iterable(ctx)
}
fn install_js_iter(self, ctx: &JSContext<V::Context>, obj: &JSObject<V>) -> JSResult<()> {
let js_iter = JSIterator::new(self, ctx);
js_iter.install_on(ctx, obj)
}
}
pub trait IntoJSAsyncIteratorExt<V, T>
where
V: JSObjectOps + 'static,
{
fn to_js_async_iter(self, ctx: &JSContext<V::Context>) -> JSResult<JSObject<V>>;
fn install_js_async_iter(self, ctx: &JSContext<V::Context>, obj: &JSObject<V>) -> JSResult<()>;
}
impl<V, T, S> IntoJSAsyncIteratorExt<V, T> for S
where
V: JSObjectOps + JSArrayOps + JSTypeOf + 'static,
T: IntoJSValue<V> + 'static,
S: Stream<Item = T> + 'static,
V::Context: JSErrorFactory,
{
fn to_js_async_iter(self, ctx: &JSContext<V::Context>) -> JSResult<JSObject<V>> {
let js_iter = JSAsyncIterator::new(self, ctx);
js_iter.to_js_async_iterable(ctx)
}
fn install_js_async_iter(self, ctx: &JSContext<V::Context>, obj: &JSObject<V>) -> JSResult<()> {
let js_iter = JSAsyncIterator::new(self, ctx);
js_iter.install_on(ctx, obj)
}
}
pub fn install_async_iterator_symbol<V: JSObjectOps + 'static>(
ctx: &JSContext<V::Context>,
obj: &JSObject<V>,
) -> JSResult<()> {
let symbol = ctx
.global()
.get::<_, JSObject<V>>("Symbol")?
.get::<_, JSSymbol<V>>("asyncIterator")?;
obj.set(
symbol,
JSFunc::new(ctx, |this: This<JSObject<V>>| this.0.clone())?,
)?;
Ok(())
}
pub fn install_iterator_symbol<V: JSObjectOps + 'static>(
ctx: &JSContext<V::Context>,
obj: &JSObject<V>,
) -> JSResult<()> {
let symbol = ctx
.global()
.get::<_, JSObject<V>>("Symbol")?
.get::<_, JSSymbol<V>>("iterator")?;
obj.set(
symbol,
JSFunc::new(ctx, |this: This<JSObject<V>>| this.0.clone())?,
)?;
Ok(())
}