extendr-api 0.9.0

Safe and user friendly bindings to the R programming language.
Documentation
//! Module metadata
//!
//! This data is returned by `get_module_metadata()`
//! which is generated by [extendr_module!].
use crate as extendr_api;
use crate::*;
use std::io::Write;

/// Metadata function argument.
#[derive(Debug, PartialEq, IntoList)]
pub struct Arg {
    pub name: &'static str,
    pub arg_type: &'static str,
    #[into_list(ignore)]
    pub default: Option<&'static str>,
}

/// Metadata function.
#[derive(Debug, PartialEq, IntoList)]
pub struct Func {
    pub doc: &'static str,
    pub rust_name: &'static str,
    pub mod_name: &'static str,
    pub r_name: &'static str,
    pub c_name: &'static str,
    pub args: Vec<Arg>,
    pub return_type: &'static str,
    #[into_list(ignore)]
    pub func_ptr: *const u8,
    pub hidden: bool,
    pub invisible: Option<bool>,
}

/// Metadata Impl.
#[derive(Debug, PartialEq, IntoList)]
pub struct Impl {
    pub doc: &'static str,
    pub name: &'static str,
    pub methods: Vec<Func>,
}

/// Module metadata.
#[derive(Debug, PartialEq, IntoList)]
pub struct Metadata {
    pub name: &'static str,
    pub functions: Vec<Func>,
    pub impls: Vec<Impl>,
}

struct RArg {
    name: String,
    default: Option<&'static str>,
}

impl RArg {
    fn is_self(&self) -> bool {
        self.name == "self"
    }

    fn to_actual_arg(&self) -> String {
        self.name.clone()
    }

    fn to_formal_arg(&self) -> String {
        match self.default {
            Some(default_val) => format!("{} = {}", self.name, default_val),
            None => self.name.clone(),
        }
    }
}

impl From<&Arg> for RArg {
    fn from(arg: &Arg) -> Self {
        Self {
            name: sanitize_identifier(arg.name),
            default: arg.default,
        }
    }
}

impl From<&Vec<Arg>> for Robj {
    fn from(args: &Vec<Arg>) -> Self {
        List::from_values(args).into()
    }
}

impl From<&Vec<Func>> for Robj {
    fn from(funcs: &Vec<Func>) -> Self {
        List::from_values(funcs).into()
    }
}

impl From<&Vec<Impl>> for Robj {
    fn from(impls: &Vec<Impl>) -> Self {
        List::from_values(impls).into()
    }
}

fn write_doc(w: &mut Vec<u8>, doc: &str) -> std::io::Result<()> {
    if !doc.is_empty() {
        write!(w, "#'")?;
        for c in doc.chars() {
            if c == '\n' {
                write!(w, "\n#'")?;
            } else {
                write!(w, "{}", c)?;
            }
        }
        writeln!(w)?;
    }
    Ok(())
}

/// Wraps invalid R identifiers, like `_function_name`, into backticks.
/// Removes raw identifiers (`r#`).
fn sanitize_identifier(name: &str) -> String {
    if name.starts_with('_') {
        format!("`{}`", name)
    } else if name.starts_with("r#") {
        name.strip_prefix("r#").unwrap().into()
    } else {
        name.to_string()
    }
}

fn join_str(input: impl Iterator<Item = String>, sep: &str) -> String {
    input.collect::<Vec<String>>().join(sep)
}

/// Generate a wrapper for a non-method function.
fn write_function_wrapper(
    w: &mut Vec<u8>,
    func: &Func,
    package_name: &str,
    use_symbols: bool,
) -> std::io::Result<()> {
    if func.hidden {
        return Ok(());
    }

    write_doc(w, func.doc)?;

    let r_args: Vec<RArg> = func.args.iter().map(Into::into).collect();
    let actual_args = r_args.iter().map(|a| a.to_actual_arg());
    let formal_args = r_args.iter().map(|a| a.to_formal_arg());

    let should_be_invisible = match func.invisible {
        Some(true) => true,
        Some(false) => false,
        None => false,
    };

    if should_be_invisible {
        write!(
            w,
            "{} <- function({}) invisible(.Call(",
            sanitize_identifier(func.r_name),
            join_str(formal_args, ", ")
        )?;
    } else {
        write!(
            w,
            "{} <- function({}) .Call(",
            sanitize_identifier(func.r_name),
            join_str(formal_args, ", ")
        )?;
    }

    if use_symbols {
        write!(w, "{}", func.c_name)?;
    } else {
        write!(w, "\"{}\"", func.c_name)?;
    }

    if !func.args.is_empty() {
        write!(w, ", {}", join_str(actual_args, ", "))?;
    }

    if !use_symbols {
        write!(w, ", PACKAGE = \"{}\"", package_name)?;
    }

    if should_be_invisible {
        writeln!(w, "))\n")?;
    } else {
        writeln!(w, ")\n")?;
    }

    Ok(())
}

/// Generate a wrapper for a method.
fn write_method_wrapper(
    w: &mut Vec<u8>,
    func: &Func,
    package_name: &str,
    use_symbols: bool,
    class_name: &str,
) -> std::io::Result<()> {
    if func.hidden {
        return Ok(());
    }

    let r_args: Vec<RArg> = func.args.iter().map(Into::into).collect();
    let actual_args = r_args.iter().map(|a| a.to_actual_arg());

    // Skip a leading "self" argument.
    // This is supplied by the environment.
    let formal_args = r_args
        .iter()
        .skip_while(|a| a.is_self())
        .map(|a| a.to_formal_arg());

    // Both `class_name` and `func.name` should be processed
    // because they are exposed to R
    let should_be_invisible = match func.invisible {
        Some(true) => true,
        Some(false) => false,
        None => false,
    };

    if should_be_invisible {
        write!(
            w,
            "{}${} <- function({}) invisible(.Call(",
            sanitize_identifier(class_name),
            sanitize_identifier(func.r_name),
            join_str(formal_args, ", ")
        )?;
    } else {
        write!(
            w,
            "{}${} <- function({}) .Call(",
            sanitize_identifier(class_name),
            sanitize_identifier(func.r_name),
            join_str(formal_args, ", ")
        )?;
    }

    // Here no processing is needed because of `wrap__` prefix
    if use_symbols {
        write!(w, "{}", func.c_name)?;
    } else {
        write!(w, "\"{}\"", func.c_name)?;
    }

    if actual_args.len() != 0 {
        write!(w, ", {}", join_str(actual_args, ", "))?;
    }

    if !use_symbols {
        write!(w, ", PACKAGE = \"{}\"", package_name)?;
    }

    if should_be_invisible {
        writeln!(w, "))\n")?;
    } else {
        writeln!(w, ")\n")?;
    }

    Ok(())
}

/// Generate a wrapper for an implementation block.
fn write_impl_wrapper(
    w: &mut Vec<u8>,
    name: &str,
    impls: &[Impl],
    package_name: &str,
    use_symbols: bool,
) -> std::io::Result<()> {
    let mut exported = false;
    {
        for imp in impls.iter().filter(|imp| imp.name == name) {
            if !exported {
                exported = imp.doc.contains("@export");
            }
            write_doc(w, imp.doc)?;
        }
    }

    let imp_name_fixed = sanitize_identifier(name);

    // Using fixed name because it is exposed to R
    writeln!(w, "{} <- new.env(parent = emptyenv())\n", imp_name_fixed)?;

    for imp in impls.iter().filter(|imp| imp.name == name) {
        for func in &imp.methods {
            // write_doc(& mut w, func.doc)?;
            // `imp.name` is passed as is and sanitized within the function
            write_method_wrapper(w, func, package_name, use_symbols, imp.name)?;
        }
    }

    if exported {
        writeln!(w, "#' @rdname {}", name)?;
        writeln!(w, "#' @usage NULL")?;
    }

    // This is needed no matter whether the user added `@export` or
    // not; even if we don't export the class itself and its
    // initializers, we always export the `$` method so the method is
    // correctly added to the NAMESPACE.
    writeln!(w, "#' @export")?;

    // LHS with dollar operator is wrapped in ``, so pass name as is,
    // but in the body `imp_name_fixed` is called as valid R function,
    // so we pass preprocessed value
    writeln!(w, "`$.{}` <- function (self, name) {{ func <- {}[[name]]; environment(func) <- environment(); func }}\n", name, imp_name_fixed)?;

    writeln!(w, "#' @export")?;
    writeln!(w, "`[[.{}` <- `$.{}`\n", name, name)?;

    Ok(())
}

impl Metadata {
    pub fn make_r_wrappers(
        &self,
        use_symbols: bool,
        package_name: &str,
    ) -> std::io::Result<String> {
        let mut w = Vec::new();

        for func in &self.functions {
            write_function_wrapper(&mut w, func, package_name, use_symbols)?;
        }

        for name in self.impl_names() {
            write_impl_wrapper(&mut w, name, &self.impls, package_name, use_symbols)?;
        }

        unsafe { Ok(String::from_utf8_unchecked(w)) }
    }

    fn impl_names(&self) -> Vec<&str> {
        let mut vec: Vec<&str> = vec![];
        for impls in &self.impls {
            if !vec.contains(&impls.name) {
                vec.push(impls.name)
            }
        }
        vec
    }
}