use crate as extendr_api;
use crate::*;
use std::io::Write;
#[derive(Debug, PartialEq, IntoList)]
pub struct Arg {
pub name: &'static str,
pub arg_type: &'static str,
#[into_list(ignore)]
pub default: Option<&'static str>,
}
#[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>,
}
#[derive(Debug, PartialEq, IntoList)]
pub struct Impl {
pub doc: &'static str,
pub name: &'static str,
pub methods: Vec<Func>,
}
#[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(())
}
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)
}
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(())
}
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());
let formal_args = r_args
.iter()
.skip_while(|a| a.is_self())
.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(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, ", ")
)?;
}
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(())
}
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);
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_method_wrapper(w, func, package_name, use_symbols, imp.name)?;
}
}
if exported {
writeln!(w, "#' @rdname {}", name)?;
writeln!(w, "#' @usage NULL")?;
}
writeln!(w, "#' @export")?;
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
}
}