[Rcpp-commits] r3862 - in pkg/Rcpp: R man src

noreply at r-forge.r-project.org noreply at r-forge.r-project.org
Tue Oct 30 23:07:09 CET 2012


Author: jjallaire
Date: 2012-10-30 23:07:09 +0100 (Tue, 30 Oct 2012)
New Revision: 3862

Modified:
   pkg/Rcpp/R/Attributes.R
   pkg/Rcpp/man/sourceCpp.Rd
   pkg/Rcpp/src/Attributes.cpp
Log:
sourceCpp: add code parameter; invisibly return list of exported functions

Modified: pkg/Rcpp/R/Attributes.R
===================================================================
--- pkg/Rcpp/R/Attributes.R	2012-10-30 18:06:41 UTC (rev 3861)
+++ pkg/Rcpp/R/Attributes.R	2012-10-30 22:07:09 UTC (rev 3862)
@@ -17,15 +17,24 @@
 
 
 # Source C++ code from a file
-sourceCpp <- function(file, 
+sourceCpp <- function(file = "",
+                      code = NULL,
                       local = FALSE, 
                       rebuild = FALSE,
                       show.output = verbose,
                       verbose = getOption("verbose")) {
     
+    # resolve code into a file if necessary
+    if (!missing(code)) {
+        file <- tempfile(fileext = ".cpp")
+        con <- file(file, open = "w")
+        writeLines(code, con)
+        close(con)
+    }
+    
     # get the context (does code generation as necessary)
     file <- normalizePath(file, winslash = "/")
-    context <- .Call("sourceCppContext", PACKAGE="Rcpp", file, .Platform)
+    context <- .Call("sourceCppContext", PACKAGE="Rcpp", file, code, .Platform)
      
     # perform a build if necessary
     if (context$buildRequired || rebuild) {
@@ -114,10 +123,40 @@
     }
     
     # source the R script
-    scriptPath <- file.path(context$buildDirectory, context$rSourceFilename)    
-    source(scriptPath, local)
+    scriptPath <- file.path(context$buildDirectory, context$rSourceFilename) 
+    if (is.environment(local))
+        source(scriptPath, local = local)
+    else if (local)
+        source(scriptPath, local = parent.frame())
+    else
+        source(scriptPath, local = FALSE)
+    
+    # return (invisibly) a list of exported functions
+    invisible(context$exportedFunctions)
 }
 
+# Define a single C++ function.
+cppFunction <- function(code) {
+    
+    # add scaffolding 
+    code <- paste("#include <Rcpp.h>", "using namespace Rcpp;",
+                  "// [[Rcpp::export]]", code, sep="\n")
+    
+    # source cpp into environment we create to hold the function
+    env <- new.env()
+    exported <- sourceCpp(code = code, local = env)
+    
+    # verify that a single function was exported and return it
+    if (length(exported) == 0)
+        stop("No function definition found")
+    else if (length(exported) > 1)
+        stop("More than one function definition")
+    else {
+        functionName <- exported[[1]]
+        get(functionName, env)
+    }
+}
+
 # Scan the source files within a package for attributes and generate code
 # based on the attributes. 
 compileAttributes <- function(pkgdir = ".", verbose = getOption("verbose")) {

Modified: pkg/Rcpp/man/sourceCpp.Rd
===================================================================
--- pkg/Rcpp/man/sourceCpp.Rd	2012-10-30 18:06:41 UTC (rev 3861)
+++ pkg/Rcpp/man/sourceCpp.Rd	2012-10-30 22:07:09 UTC (rev 3862)
@@ -4,17 +4,20 @@
 Source C++ Code from a File
 }
 \description{
-\code{sourceCpp} parses the specified C++ file and looks for functions marked with the \code{\link[=exportAttribute]{Rcpp::export}} attribute. The C++ file is then built into a shared library and its exported functions are made available as R functions in the specified environment.
+\code{sourceCpp} parses the specified C++ file or source code and looks for functions marked with the \code{\link[=exportAttribute]{Rcpp::export}} attribute. A shared library is then built and its exported functions are made available as R functions in the specified environment.
 }
 \usage{
-sourceCpp(file, local = FALSE, rebuild = FALSE, 
+sourceCpp(file, code = NULL, local = FALSE, rebuild = FALSE, 
           show.output = verbose, verbose = getOption("verbose"))
 }
 %- maybe also 'usage' for other objects documented here.
 \arguments{
   \item{file}{
-    A character string giving the pathname of a file.
+    A character string giving the pathname of a file
 }
+  \item{code}{
+    A character string with C++ source code to compile
+}
   \item{local}{
     \code{TRUE}, \code{FALSE} or an environment, determining where the R functions will be made available. \code{FALSE} (the default) corresponds to the user's workspace (the global environment) and \code{TRUE} to the environment from which source is called
 }
@@ -29,11 +32,19 @@
 }
 }
 \details{
+    If the \code{code} parameter is provided then the \code{file} parameter is ignored.
+
     Functions marked with the \code{\link[=exportAttribute]{Rcpp::export}} attribute must have return types that are compatible with \code{Rcpp::wrap} and parameter types that are compatible with \code{Rcpp::as}.
     
-    If the source file has compilation dependencies on other packages (e.g. \pkg{Matrix}, \pkg{RcppArmadillo}) then an \code{\link[=dependsAttribute]{Rcpp::depends}} attribute should be added to the file naming these dependencies.
+    If the code has compilation dependencies on other packages (e.g. \pkg{Matrix}, \pkg{RcppArmadillo}) then an \code{\link[=dependsAttribute]{Rcpp::depends}} attribute should be added naming these dependencies.
+    
+    The \code{sourceCpp} function will not rebuild the shared library if the underlying code has not changed since the last compilation.
 }
 
+\value{
+    Returns (invisibly) a character vector with the names of the R functions that were sourced into the specified environment.
+}
+
 \seealso{
 \code{\link[=exportAttribute]{Rcpp::export}}, \code{\link[=dependsAttribute]{Rcpp::depends}}
 }
@@ -41,6 +52,18 @@
 \examples{
 \dontrun{
 
-sourceCpp("transform.cpp")
+sourceCpp("fibonacci.cpp")
+
+sourceCpp(code='
+  #include <Rcpp.h>
+
+  // [[Rcpp::export]]
+  int fibonacci(const int x) {
+    if (x == 0) return(0);
+    if (x == 1) return(1);
+    return (fibonacci(x - 1)) + fibonacci(x - 2);
+  }'
+)
+
 }
 }

Modified: pkg/Rcpp/src/Attributes.cpp
===================================================================
--- pkg/Rcpp/src/Attributes.cpp	2012-10-30 18:06:41 UTC (rev 3861)
+++ pkg/Rcpp/src/Attributes.cpp	2012-10-30 22:07:09 UTC (rev 3862)
@@ -879,6 +879,14 @@
     return ostr.str();
 }
 
+// Determine the exported name for a function 
+std::string exportedName(const Attribute& attribute) {   
+    if (!attribute.params().empty())
+        return attribute.params()[0].name();
+    else
+        return attribute.function().name();
+}
+
 // Generate R functions from the passed attributes
 std::string generateRFunctions(const SourceFileAttributes& attributes,
                                const std::string& contextId,
@@ -917,9 +925,7 @@
         std::string args = argsOstr.str();
         
         // determine the function name
-        std::string name = function.name();
-        if (!attribute.params().empty())
-            name = attribute.params()[0].name();
+        std::string name = exportedName(attribute);
             
         // write the function - use contextId to ensure symbol uniqueness
         ostr << name << " <- function(" << args << ") {" 
@@ -1062,16 +1068,19 @@
         rOfs << rCode;
         rOfs.close();
         
-        // look for depends attributes
+        // enumerate exported functions and dependencies
+        exportedFunctions_.clear();
         depends_.clear();
         for (SourceFileAttributes::const_iterator 
           it = sourceAttributes.begin(); it != sourceAttributes.end(); ++it) {
-             if (it->name() == kDependsAttribute) {
+             if (it->name() == kExportAttribute && !it->function().empty()) 
+                exportedFunctions_.push_back(exportedName(*it));
+            
+             else if (it->name() == kDependsAttribute) {
                  for (size_t i = 0; i<it->params().size(); ++i)
                     depends_.push_back(it->params()[i].name());
              }   
         }
-        
     }
     
     const std::string& cppSourcePath() const {
@@ -1102,6 +1111,10 @@
         return buildDirectory_ + fileSep_ + dynlibFilename();
     }
     
+    const std::vector<std::string>& exportedFunctions() const {
+        return exportedFunctions_;
+    }
+    
     const std::vector<std::string>& depends() const { return depends_; };
       
 private:
@@ -1123,10 +1136,59 @@
     std::string buildDirectory_;
     std::string fileSep_;
     std::string dynlibExt_;
+    std::vector<std::string> exportedFunctions_;
     std::vector<std::string> depends_;
 };
 
+// Dynlib cache that allows lookup by either file path or code contents
+class SourceCppDynlibCache {
+  
+public:
+    // Insert into cache by file name
+    void insertFile(const std::string& file, const SourceCppDynlib& dynlib) {
+        Entry entry;
+        entry.file = file;
+        entry.dynlib = dynlib;
+        entries_.push_back(entry);
+    }
+    
+    // Insert into cache by code
+    void insertCode(const std::string& code, const SourceCppDynlib& dynlib) {
+        Entry entry;
+        entry.code = code;
+        entry.dynlib = dynlib;
+        entries_.push_back(entry);
+    }
 
+    // Lookup by file
+    SourceCppDynlib lookupByFile(const std::string& file) {
+        for (std::size_t i = 0; i < entries_.size(); i++) {
+            if (entries_[i].file == file)
+                return entries_[i].dynlib;
+        }
+        
+        return SourceCppDynlib();
+    }
+    
+    // Lookup by code
+    SourceCppDynlib lookupByCode(const std::string& code) {
+        for (std::size_t i = 0; i < entries_.size(); i++) {
+            if (entries_[i].code == code)
+                return entries_[i].dynlib;
+        }
+        
+        return SourceCppDynlib();
+    }
+  
+private:
+    struct Entry {
+        std::string file;
+        std::string code;
+        SourceCppDynlib dynlib;
+    };
+    std::vector<Entry> entries_;
+};
+
 // Class which manages writing of code for compileAttributes
 class ExportsStream {
 public:
@@ -1246,16 +1308,21 @@
 
 // Create temporary build directory, generate code as necessary, and return
 // the context required for the sourceCpp function to complete it's work
-RcppExport SEXP sourceCppContext(SEXP sFile, SEXP sPlatform) {
+RcppExport SEXP sourceCppContext(SEXP sFile, SEXP sCode, SEXP sPlatform) {
 BEGIN_RCPP
     // parameters
     std::string file = Rcpp::as<std::string>(sFile);
+    std::string code = sCode != R_NilValue ? Rcpp::as<std::string>(sCode) : "";
     Rcpp::List platform = Rcpp::as<Rcpp::List>(sPlatform);
     
     // get dynlib (using cache if possible)
-    static std::map<std::string,SourceCppDynlib> s_dynlibs;
-    SourceCppDynlib dynlib = s_dynlibs[file];
-    
+    static SourceCppDynlibCache s_dynlibCache;
+    SourceCppDynlib dynlib;
+    if (!code.empty())
+        dynlib = s_dynlibCache.lookupByCode(code);
+    else
+        dynlib = s_dynlibCache.lookupByFile(file);
+  
     // check dynlib build state
     bool buildRequired = false;
     
@@ -1263,7 +1330,10 @@
     if (dynlib.isEmpty()) {
         buildRequired = true;
         dynlib = SourceCppDynlib(file, platform);
-        s_dynlibs[file] = dynlib;
+        if (!code.empty())
+            s_dynlibCache.insertCode(code, dynlib);
+        else
+            s_dynlibCache.insertFile(file, dynlib);
     }    
         
     // if the cached dynlib is dirty then regenerate the source
@@ -1283,6 +1353,7 @@
     context["buildRequired"] = buildRequired;
     context["buildDirectory"] = dynlib.buildDirectory();
     context["generatedCpp"] = dynlib.generatedCpp();
+    context["exportedFunctions"] = dynlib.exportedFunctions();
     context["cppSourceFilename"] = dynlib.cppSourceFilename();
     context["rSourceFilename"] = dynlib.rSourceFilename();
     context["dynlibFilename"] = dynlib.dynlibFilename();



More information about the Rcpp-commits mailing list