[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