extendr-api 0.3.1

Safe and user friendly bindings to the R programming language.
//! A pairlist is a linked list of values with optional symbol tags.

use super::*;

#[derive(PartialEq, Clone)]
pub struct Pairlist {
    pub(crate) robj: Robj,
}

impl Pairlist {
    pub fn new() -> Self {
        let robj = Robj::from(());
        Self { robj }
    }

    /// Convert an iterator of names and values to a pairlist object.
    /// ```
    /// use extendr_api::prelude::*;
    /// test! {
    ///     let pairs = (0..100).map(|i| (format!("n{}", i), i));
    ///     let pairlist = Pairlist::from_pairs(pairs);
    ///     assert_eq!(pairlist.len(), 100);
    ///
    ///     // Use "" to indicate the absense of the name
    ///     let unnamed_pairlist = Pairlist::from_pairs([("", "a"), ("", "b")]);
    ///     assert_eq!(call!("names", unnamed_pairlist)?, r!(NULL));
    ///     let unnamed_pairlist_r = R!(r#"pairlist("a", "b")"#)?.as_pairlist().unwrap();
    ///     assert_eq!(unnamed_pairlist_r.names().collect::<Vec<_>>(), vec!["", ""]);
    /// }
    /// ```
    pub fn from_pairs<NV>(pairs: NV) -> Self
    where
        NV: IntoIterator,
        NV::IntoIter: DoubleEndedIterator,
        NV::Item: SymPair,
    {
        crate::single_threaded(|| unsafe {
            let mut num_protects = 0;
            let mut res = R_NilValue;
            for nv in pairs.into_iter().rev() {
                let (name, val) = nv.sym_pair();
                let val = Rf_protect(val.get());
                res = Rf_protect(Rf_cons(val, res));
                num_protects += 2;
                if let Some(name) = name {
                    SET_TAG(res, name.get());
                }
            }
            let res = Pairlist {
                robj: Robj::from_sexp(res),
            };
            Rf_unprotect(num_protects as i32);
            res
        })
    }

    /// Generate paits of names and values.
    /// ```
    /// use extendr_api::prelude::*;
    /// test! {
    ///     let pairs = (0..100).map(|i| (format!("n{}", i), i));
    ///     let pairlist = Pairlist::from_pairs(pairs);
    ///     assert_eq!(pairlist.iter().count(), 100);
    ///     assert_eq!(pairlist.iter().nth(50), Some(("n50", r!(50))));
    /// }
    /// ```
    pub fn iter(&self) -> PairlistIter {
        unsafe {
            PairlistIter {
                robj: self.robj.clone(),
                list_elem: self.robj.get(),
            }
        }
    }

    pub fn names(&self) -> impl Iterator<Item = &'static str> {
        self.iter().map(|(tag, _)| tag)
    }

    pub fn values(&self) -> impl Iterator<Item = Robj> {
        self.iter().map(|(_, robj)| robj)
    }
}

impl Default for wrapper::pairlist::Pairlist {
    fn default() -> Self {
        Self::new()
    }
}

/// Generate paits of names and values.
/// ```
/// use extendr_api::prelude::*;
/// test! {
///     let pairs = (0..100).map(|i| (format!("n{}", i), i));
///     let pairlist = Pairlist::from_pairs(pairs);
///     assert_eq!(pairlist.iter().count(), 100);
///     assert_eq!(pairlist.iter().nth(50), Some(("n50", r!(50))));
/// }
/// ```
#[derive(Clone)]
pub struct PairlistIter {
    pub(crate) robj: Robj,
    pub(crate) list_elem: SEXP,
}

impl Default for PairlistIter {
    fn default() -> Self {
        PairlistIter::new()
    }
}

impl PairlistIter {
    /// Make an empty pairlist iterator.
    pub fn new() -> Self {
        unsafe {
            Self {
                robj: ().into(),
                list_elem: R_NilValue,
            }
        }
    }
}

impl Iterator for PairlistIter {
    // Note: The static is bad here, but we await RFC 1598
    // to do this properly. Howevere, symbols live forever.
    // https://github.com/rust-lang/rfcs/blob/master/text/1598-generic_associated_types.md
    type Item = (&'static str, Robj);

    fn next(&mut self) -> Option<Self::Item> {
        unsafe {
            let sexp = self.list_elem;
            if sexp == R_NilValue {
                None
            } else {
                let tag = TAG(sexp);
                let value = Robj::from_sexp(CAR(sexp));
                self.list_elem = CDR(sexp);
                if TYPEOF(tag) == SYMSXP as i32 {
                    let printname = PRINTNAME(tag);
                    assert!(TYPEOF(printname) as u32 == CHARSXP);
                    let name = to_str(R_CHAR(printname) as *const u8);
                    Some((std::mem::transmute(name), value))
                } else {
                    // empty string represents the absense of the name
                    Some(("", value))
                }
            }
        }
    }
}

impl IntoIterator for Pairlist {
    type IntoIter = PairlistIter;
    type Item = (&'static str, Robj);

    /// Convert a PairList into an interator, consuming the pairlist.
    /// ```
    /// use extendr_api::prelude::*;
    /// test! {
    ///     let pairlist = pairlist!(a=1, 2).as_pairlist().unwrap();
    ///     let vec : Vec<_> = pairlist.into_iter().collect();
    ///     assert_eq!(vec, vec![("a", r!(1)), ("", r!(2))]);
    /// }
    /// ```
    fn into_iter(self) -> Self::IntoIter {
        unsafe {
            let sexp = self.robj.get();
            PairlistIter {
                robj: self.robj,
                list_elem: sexp,
            }
        }
    }
}

impl TryFrom<&Robj> for PairlistIter {
    type Error = Error;

    /// You can pass a PairlistIter to a function.
    fn try_from(robj: &Robj) -> Result<Self> {
        let pairlist: Pairlist = robj.try_into()?;
        Ok(pairlist.into_iter())
    }
}

impl From<PairlistIter> for Robj {
    /// You can return a PairlistIter from a function.
    fn from(iter: PairlistIter) -> Self {
        iter.robj
    }
}

impl From<()> for Pairlist {
    /// Construct a NULL pairlist (which is a NULL).
    fn from(_: ()) -> Self {
        Pairlist {
            robj: Robj::from(()),
        }
    }
}

impl std::fmt::Debug for Pairlist {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "pairlist!({})",
            self.iter()
                .map(|(k, v)| format!("{}={:?}", k, v))
                .collect::<Vec<_>>()
                .join(", ")
        )?;
        Ok(())
    }
}