R/registration.R

Defines functions make_dotcall_to_fortran make_fun_list make_call_args make_sexp_args make_c_args gen_registration

Documented in gen_registration make_call_args make_c_args make_dotcall_to_fortran make_fun_list make_sexp_args

#' Generate registration from fortran subroutine
#'
#' This uses uses implicit fortran conventions
#' @param pkg_name the package name as character
#' @param fun_list the list of fortran function prototypes, see
#'     example
#' @param callEntries a character vector defining code that will be included verbatim that will define the C array `CallEntries[]`, default `NULL`
#' @return lines that can be written into an init file for the package
#' @export
#' @examples
#' ## Most common use
#' fns <- c("subroutine pclasso(no,ni,x,y,w,theta,ng,mg,aa,ne,nx,nlam,ulam,thr,maxit,verbose,ao,ia,kin,nlp,jerr)", "subroutine logpclasso(no,ni,x,y,w,theta,ng,mg,aa,ne,nx,nlam,ulam,thr,maxit,verbose,a0,ao,ia,kin,nlp,jerr)")
#' # Generate lines that can be written into pcLasso/src/pcLasso_init.c for example
#' gen_registration("pcLasso", fns)
#'
#' ## Example using callEntries
#' callEntries <- c(
#'   'extern SEXP store_rfun(SEXP rfun);',
#'   'static const R_CallMethodDef CallEntries[] = {',
#'      '{"store_rfun", (DL_FUNC) &store_rfun, 1}',
#'      '{NULL, NULL, 0}',
#'   '};')
#' gen_registration("pcLasso", fns, callEntries = callEntries)
#'
gen_registration <- function(pkg_name, fun_list, callEntries = NULL) {
    cat("Generating Init function for package", pkg_name, "\n")
    pkg_name_pattern_uc <- "R_PKG_NAME"
    pkg_name_pattern <- "r_pkg_name"

    pkg_name_uc <- toupper(pkg_name)

    prolog <- c(
        "// Automatically generated by SUtools, editing not advised.",
        '#ifndef R_R_PKG_NAME_H',
        '#define R_R_PKG_NAME_H',
        '#include <R.h>',
        '#include <Rinternals.h>',
        '#include <R_ext/Rdynload.h>',
        '#ifdef ENABLE_NLS',
        '#include <libintl.h>',
        '#define _(String) dgettext ("r_pkg_name", String)',
        '#else',
        '#define _(String) (String)',
        '#endif',
        '',
        callEntries,
        '',
        '#define FDEF(name)  {#name, (DL_FUNC) &F77_SUB(name), sizeof(name ## _t)/sizeof(name ## _t[0]), name ##_t}')

    prolog <- gsub(pkg_name_pattern_uc, pkg_name_uc, prolog)
    prolog <- gsub(pkg_name_pattern, pkg_name, prolog)

    gen_types <- function(name, args, ...) {
        ## Quite primitive, but will do the job mostly
        doubles <- grep("^[[:space:]]*double", args)
        ints <- grep("^[[:space:]]*int", args)
        chars <- grep("^[[:space:]]*char", args)
        delims <- c(rep(",", length(args) - 1), "")
        out1 <- c(
            sprintf("void F77_SUB(%s)(", name),
            paste0(args, delims),
            ");")
        argOut <- rep("REALSXP", length(args))
        argOut[ints] <- "INTSXP"
        argOut[chars] <- "STRSXP"
        out2 <- c(
            sprintf("static R_NativePrimitiveArgType %s_t[] = {", name),
            paste0(argOut, delims),
            "};")
        c(out1, " ", out2)
    }
    fun_list <- make_fun_list(fun_list)
    out3 <- sapply(fun_list, function(x) sprintf("FDEF(%s)", x$name))


    epilog <- c(
        'void R_init_r_pkg_name(DllInfo *dll){',
        ifelse(is.null(callEntries),
               '  R_registerRoutines(dll, NULL, NULL, fMethods, NULL);',
               '  R_registerRoutines(dll, NULL, callEntries, fMethods, NULL);'),
        '  R_useDynamicSymbols(dll, FALSE);',
        '}',
        '#endif')

    epilog <- gsub(pkg_name_pattern_uc, pkg_name_uc, epilog)
    epilog <- gsub(pkg_name_pattern, pkg_name, epilog)

    c(prolog,
      unlist(lapply(fun_list, function(x) do.call(gen_types, x))),
      "",
      "static R_FortranMethodDef fMethods[] = {",
      paste(out3, ","),
      "{NULL, NULL, 0}",
      "};",
      "",
      epilog)
}


#' Make C argument types
#'
#' This uses implicit fortran conventions, that is
#' integers for variables prefixed with i through n and reals for others
#' @param ... list of variable names, as is
#' @import stringr
#' @export
#' @examples
#' make_c_args(no,ni,x,y,w,theta,ng,mg,aa,ne,nx,nlam,ulam,thr,maxit,verbose,ao,ia,kin,nlp,jerr)
#'
#'
make_c_args <-    function(...){
    ## my own little helper function
    args <- stringr::str_trim(as.character(substitute(list(...)))[-1])
    int_type <- substring(args, 1, 1) %in% c("i","j","k","l","m","n")
    type <- ifelse(int_type, "int", "double")
    paste0(type, " *", args)
}

#' Make SEXP argument types
#'
#' @param ... list of variable names, as is
#' @import stringr
#' @export
#' @examples
#' make_sexp_args(no,ni,x,y,w,theta,ng,mg,aa,ne,nx,nlam,ulam,thr,maxit,verbose,ao,ia,kin,nlp,jerr)
#'
#'
make_sexp_args <-    function(...){
    ## my own little helper function
    args <- stringr::str_trim(as.character(substitute(list(...)))[-1])
  paste0("SEXP ", args)
}

#' Make call argument types
#'
#' This uses implicit fortran conventions, that is
#' integers for variables prefixed with i through n and reals for others
#' @param ... list of variable names, as is
#' @import stringr
#' @export
#' @examples
#' make_call_args(no,ni,x,y,w,theta,ng,mg,aa,ne,nx,nlam,ulam,thr,maxit,verbose,ao,ia,kin,nlp,jerr)
#'
#'
make_call_args <-    function(...){
    ## my own little helper function
    args <- stringr::str_trim(as.character(substitute(list(...)))[-1])
    int_type <- substring(args, 1, 1) %in% c("i","j","k","l","m","n")
  type <- ifelse(int_type, "INTEGER", "REAL")
  paste0(type, "(", args, ")")
}


#' Generate function name and C arg types from subroutine/entry definition
#'
#' Parse a fortran subroutine/entry definition and return subroutine/entry name and
#' C argument types as a list
#' @param fun_str a vector of subroutine definition strings with args
#' @importFrom stringr str_match
#' @return a list of function names along with C argument types, SEXP args, and .Call args
#' @export
#' @examples
#' make_fun_list(c("subroutine foo(no,ni,x,y,w,theta)", "entry foobar(ix, y)", "subroutine bar(x,n,p,m)"))
#'
#'
make_fun_list <- function(fun_strs) {
    lapply(fun_strs,
           function(fun_str) {
               fun <- stringr::str_match(fun_str, "(subroutine|entry)\\s+(\\w+)\\s*\\(")[, 3]
               args <-  strsplit(stringr::str_match(fun_str, "\\((.*)\\)"), ",")[[2]]
               list(name = fun, args = do.call(make_c_args, as.list(args)),
                    sexp_args = do.call(make_sexp_args, as.list(args)),
                    call_args = do.call(make_call_args, as.list(args)),
                    arg_names = args)
           })
}

#' Generate C stub and R stub for making a .Call to a Fortran function
#'
#' Parse a fortran subroutine/entry definition and return a C routine that can be used with .Call and R call snippet
#' @param fun_str a vector of subroutine definition strings with args
#' @importFrom stringr str_match
#' @return a named list of C code and R code for each function
#' @export
#' @examples
#' make_dotcall_to_fortran(c("subroutine foo(no,ni,x,y,w,theta)", "entry foobar(ix, y)", "subroutine bar(x,n,p,m)"))
#'
make_dotcall_to_fortran <- function(fun_strs) {
  funs <- sapply(fun_strs, function(fun_str) stringr::str_match(fun_str, "(subroutine|entry)\\s+(\\w+)\\s*\\(")[, 3])
  result  <- lapply(fun_strs,
                    function(fun_str) {
                      fun <- stringr::str_match(fun_str, "(subroutine|entry)\\s+(\\w+)\\s*\\(")[, 3]
                      args <-  strsplit(stringr::str_match(fun_str, "\\((.*)\\)"), ",")[[2]]
                      sexp_args  <- do.call(make_sexp_args, as.list(args))
                      call_args  <- do.call(make_call_args, as.list(args))

                      list(c = c(sprintf("SEXP c_%s(%s) {", fun, paste(sexp_args, collapse = ",")),
                                 sprintf("\tF77_NAME(%s)(%s);", fun, paste(call_args, collapse = ",")),
                                 sprintf("\treturn(R_NilValue);"),
                                 "}"
                                 ),
                           r = c(sprintf("result <- list(%s)", paste0(args, "=", args, collapse=",")),
                                 sprintf(".Call('c_%s', %s)", fun, paste0("result$", args, collapse = ","))
                                 )
                           )
                    })
  names(result)  <- funs
  result
}
bnaras/SUtools documentation built on Nov. 26, 2022, 6:07 p.m.