DelSkayn / rquickjs

High level bindings to the quickjs javascript engine
MIT License
431 stars 58 forks source link

Add iterator wrapper #318

Open Sytten opened 1 month ago

Sytten commented 1 month ago

Creating iterators is a pain right now. A basic example:

 pub fn entries<'js>(&self, ctx: Ctx<'js>, this: This<Self>) -> Result<Object<'js>> {
        let res = Object::new(ctx)?;

        res.set("position", 0usize)?;
        res.set(
            PredefinedAtom::SymbolIterator,
            Func::from(|it: This<Object<'js>>| -> Result<Object<'js>> { Ok(it.0) }),
        )?;
        res.set(
            PredefinedAtom::Next,
            Func::from(
                move |ctx: Ctx<'js>, it: This<Object<'js>>| -> Result<Object<'js>> {
                    let position = it.get::<_, usize>("position")?;
                    let res = Object::new(ctx.clone())?;
                    if this.data.len() <= position {
                        res.set(PredefinedAtom::Done, true)?;
                    } else {
                        let (name, value) = &this.data[position];
                        res.set(
                            "value",
                            vec![
                                rquickjs::String::from_str(ctx.clone(), name),
                                rquickjs::String::from_str(ctx, value),
                            ],
                        )?;
                        it.set("position", position + 1)?;
                    }
                    Ok(res)
                },
            ),
        )?;
        Ok(res)
    }

I tried creating a wrapper, but I can't get it to play nice:

/// A trait for converting a Rust
pub trait IntoJsIter<'js> {
    type Item: IntoJs<'js>;

    /// Call the function with the given parameters.
    fn next(&mut self, ctx: Ctx<'js>, position: usize) -> Result<Option<Self::Item>>;
}

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[repr(transparent)]
pub struct Iterator<'js>(pub(crate) Object<'js>);

impl<'js> Iterator<'js> {
    /// Create a new function from a Rust function which implements [`IntoJsFunc`].
    pub fn new<T, I>(ctx: Ctx<'js>, mut it: T) -> Result<Self>
    where
        T: IntoJsIter<'js, Item = I> + 'js,
        I: IntoJs<'js>,
    {
        let iterator = Object::new(ctx)?;
        iterator.set("position", 0usize)?;
        iterator.set(
            "next",
            Func::from(
                |ctx: Ctx<'js>, this: This<Object<'js>>| -> Result<Object<'js>> {
                    let position = this.get::<_, usize>("position")?;
                    let res = Object::new(ctx.clone())?;
                    if let Some(value) = it.next(ctx, position)? {
                        res.set("value", value)?;
                        this.set("position", position + 1)?;
                    } else {
                        res.set(PredefinedAtom::Done, true)?;
                    }
                    Ok(res)
                },
            ),
        )?;

        Ok(Self(iterator))
    }
}

impl<'js, T: StdIterator<Item = I>, I: IntoJs<'js>> IntoJsIter<'js> for T {
    type Item = T::Item;

    fn next(&mut self, _ctx: Ctx<'_>, _position: usize) -> Result<Option<Self::Item>> {
        Ok(self.next())
    }
}
Sytten commented 1 month ago

I am using a lot of them in https://github.com/DelSkayn/rquickjs/pull/319

DelSkayn commented 3 weeks ago

Hmm, I am not sure what the right API for this is.

Could you maybe elaborate a bit more on what functionality you expect? Do you want a method for easy conversion between rust iterators and JavaScript iterators? Or something else?

Sytten commented 3 weeks ago

I would say check what I did in #320, it is pretty much done. It allows both essentially

Some things are tricky when you want to avoid a clone of the data, you need to take a This reference of the parent into the children iterator like I did here: https://github.com/DelSkayn/rquickjs/pull/319/files#diff-653c6889323b6067b2be818f8ff00f785927ae3beaaeaba4a034feb5ea154d4eR116-R148 There might be a better way though.