// attributes.cpp: Rcpp R/C++ interface class library -- Rcpp attributes // // Copyright (C) 2012 - 2020 JJ Allaire, Dirk Eddelbuettel and Romain Francois // Copyright (C) 2021 - 2026 JJ Allaire, Dirk Eddelbuettel, Romain Francois, IƱaki Ucar and Travers Ching // // This file is part of Rcpp. // // Rcpp is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 2 of the License, or // (at your option) any later version. // // Rcpp is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Rcpp. If not, see . #define COMPILING_RCPP #include #include #include #include #include #include #include #include #include #include // for std::endl #include #include #define RCPP_NO_SUGAR #include /******************************************************************* * AttributesUtil.h *******************************************************************/ namespace Rcpp { namespace attributes { // Utility class for getting file existence and last modified time class FileInfo { public: // create from path explicit FileInfo(const std::string& path); // create from R list explicit FileInfo(const List& fileInfo) { // #nocov start path_ = as(fileInfo["path"]); exists_ = as(fileInfo["exists"]); lastModified_ = as(fileInfo["lastModified"]); } // #nocov end // convert to R list List toList() const { List fileInfo; fileInfo["path"] = path_; fileInfo["exists"] = exists_; fileInfo["lastModified"] = lastModified_; return fileInfo; } std::string path() const { return path_; } bool exists() const { return exists_; } double lastModified() const { return lastModified_; } std::string extension() const { std::string::size_type pos = path_.find_last_of('.'); if (pos != std::string::npos) return path_.substr(pos); else return ""; // #nocov } bool operator<(const FileInfo& other) const { return path_ < other.path_; }; bool operator==(const FileInfo& other) const { return path_ == other.path_ && exists_ == other.exists_ && lastModified_ == other.lastModified_; }; bool operator!=(const FileInfo& other) const { return ! (*this == other); }; std::ostream& operator<<(std::ostream& os) const { os << path_; return os; } private: std::string path_; bool exists_; double lastModified_; }; // Remove a file bool removeFile(const std::string& path); // Recursively create a directory void createDirectory(const std::string& path); // Known whitespace chars extern const char * const kWhitespaceChars; // Query whether a character is whitespace bool isWhitespace(char ch); // Trim a string void trimWhitespace(std::string* pStr); // Strip trailing line comments void stripTrailingLineComments(std::string* pStr); // Strip balanced quotes from around a string (assumes already trimmed) void stripQuotes(std::string* pStr); // is the passed string quoted? bool isQuoted(const std::string& str); // does a string end with another string? bool endsWith(const std::string& str, const std::string& suffix); // show a warning message void showWarning(const std::string& msg); // is the line a C++ roxygen comment? (started with //') bool isRoxygenCpp(const std::string& str); } // namespace attributes } // namespace Rcpp /******************************************************************* * AttributesTypes.h *******************************************************************/ namespace Rcpp { namespace attributes { // Known attribute names & parameters const char * const kExportAttribute = "export"; const char * const kExportName = "name"; const char * const kExportRng = "rng"; const char * const kExportInvisible = "invisible"; const char * const kExportSignature = "signature"; const char * const kInitAttribute = "init"; const char * const kDependsAttribute = "depends"; const char * const kPluginsAttribute = "plugins"; const char * const kInterfacesAttribute = "interfaces"; const char * const kInterfaceR = "r"; const char * const kInterfaceCpp = "cpp"; const char * const kParamValueFalse = "false"; const char * const kParamValueTrue = "true"; const char * const kParamValueFALSE = "FALSE"; const char * const kParamValueTRUE = "TRUE"; const char * const kParamBlockStart = "{;"; const char * const kParamBlockEnd = "}"; // Type info class Type { public: Type(): isConst_(false), isReference_(false) {} Type(const std::string& name, bool isConst, bool isReference) : name_(name), isConst_(isConst), isReference_(isReference) { } bool empty() const { return name().empty(); } bool operator==(const Type& other) const { // #nocov start return name_ == other.name_ && isConst_ == other.isConst_ && isReference_ == other.isReference_; }; // #nocov end bool operator!=(const Type& other) const { return !(*this == other); }; const std::string& name() const { return name_; } std::string full_name() const { std::string res ; if( isConst() ) res += "const " ; res += name() ; if( isReference() ) res += "&" ; return res ; } bool isVoid() const { return name() == "void"; } bool isConst() const { return isConst_; } bool isReference() const { return isReference_; } private: std::string name_; bool isConst_; bool isReference_; }; // Argument info class Argument { public: Argument() {} Argument(const std::string& name, const Type& type, const std::string& defaultValue) : name_(name), type_(type), defaultValue_(defaultValue) { } bool empty() const { return type().empty(); } bool operator==(const Argument& other) const { // #nocov start return name_ == other.name_ && type_ == other.type_ && defaultValue_ == other.defaultValue_; }; // #nocov end bool operator!=(const Argument& other) const { return !(*this == other); }; const std::string& name() const { return name_; } const Type& type() const { return type_; } const std::string& defaultValue() const { return defaultValue_; } private: std::string name_; Type type_; std::string defaultValue_; }; // Function info class Function { public: Function() {} Function(const Type& type, const std::string& name, const std::vector& arguments) : type_(type), name_(name), arguments_(arguments) { } Function renamedTo(const std::string& name) const { // #nocov start return Function(type(), name, arguments()); } std::string signature() const { return signature(name()); } std::string signature(const std::string& name) const; bool isHidden() const { return name().find_first_of('.') == 0; } // #nocov end bool empty() const { return name().empty(); } bool operator==(const Function& other) const { // #nocov start return type_ == other.type_ && name_ == other.name_ && arguments_ == other.arguments_; }; // #nocov end bool operator!=(const Function& other) const { return !(*this == other); }; const Type& type() const { return type_; } const std::string& name() const { return name_; } const std::vector& arguments() const { return arguments_; } private: Type type_; std::string name_; std::vector arguments_; }; // Attribute parameter (with optional value) class Param { public: Param() {} explicit Param(const std::string& paramText); bool empty() const { return name().empty(); } bool operator==(const Param& other) const { // #nocov start return name_ == other.name_ && value_ == other.value_; }; // #nocov end bool operator!=(const Param& other) const { return !(*this == other); }; const std::string& name() const { return name_; } const std::string& value() const { return value_; } // #nocov private: std::string name_; std::string value_; }; // Attribute (w/ optional params and signature of function it qualifies) class Attribute { public: Attribute() {} Attribute(const std::string& name, const std::vector& params, const Function& function, const std::vector& roxygen) : name_(name), params_(params), function_(function), roxygen_(roxygen) { } bool empty() const { return name().empty(); } // #nocov start bool operator==(const Attribute& other) const { return name_ == other.name_ && params_ == other.params_ && function_ == other.function_ && roxygen_ == other.roxygen_; }; // #nocov end bool operator!=(const Attribute& other) const { return !(*this == other); }; const std::string& name() const { return name_; } const std::vector& params() const { return params_; } Param paramNamed(const std::string& name) const; bool hasParameter(const std::string& name) const { return !paramNamed(name).empty(); } const Function& function() const { return function_; } bool isExportedFunction() const { return (name() == kExportAttribute) && !function().empty(); } std::string exportedName() const { // check for explicit name parameter if (hasParameter(kExportName)) { return paramNamed(kExportName).value(); } // otherwise un-named parameter in the first slot else if (!params().empty() && params()[0].value().empty()) { return params()[0].name(); } // otherwise the actual function name { return function().name(); } } std::string exportedCppName() const { std::string name = exportedName(); std::replace(name.begin(), name.end(), '.', '_'); return name; } bool rng() const { Param rngParam = paramNamed(kExportRng); if (!rngParam.empty()) return rngParam.value() == kParamValueTrue || rngParam.value() == kParamValueTRUE; else return true; } bool invisible() const { Param invisibleParam = paramNamed(kExportInvisible); if (!invisibleParam.empty()) return invisibleParam.value() == kParamValueTrue || invisibleParam.value() == kParamValueTRUE; else return false; } const std::vector& roxygen() const { return roxygen_; } std::string customRSignature() const { Param sigParam = paramNamed(kExportSignature); std::string sig = sigParam.value(); trimWhitespace(&sig); if (sig.empty()) return sig; if (sig.back() == '}') sig = sig.substr(0, sig.size()-1); // check sig.empty again since we deleted an element if (sig.empty()) return sig; if (sig.front() == '{') sig.erase(0,1); return sig; } private: std::string name_; std::vector params_; Function function_; std::vector roxygen_; }; // Operator << for parsed types std::ostream& operator<<(std::ostream& os, const Type& type); std::ostream& operator<<(std::ostream& os, const Argument& argument); std::ostream& operator<<(std::ostream& os, const Function& function); std::ostream& operator<<(std::ostream& os, const Param& param); std::ostream& operator<<(std::ostream& os, const Attribute& attribute); // interface to source file attributes class SourceFileAttributes { public: virtual ~SourceFileAttributes() {}; virtual const std::string& sourceFile() const = 0; virtual bool hasInterface(const std::string& name) const = 0; typedef std::vector::const_iterator const_iterator; virtual const_iterator begin() const = 0; virtual const_iterator end() const = 0; virtual const std::vector& modules() const = 0; virtual const std::vector >& roxygenChunks() const = 0; virtual bool hasGeneratorOutput() const = 0; virtual bool hasPackageInit() const = 0; }; } // namespace attributes } // namespace Rcpp /******************************************************************* * AttributesParser.h *******************************************************************/ namespace Rcpp { namespace attributes { // Helper class for determining whether we are in a comment class CommentState { public: CommentState() : inComment_(false) {} private: // prohibit copying CommentState(const CommentState&); CommentState& operator=(const CommentState&); public: bool inComment() const { return inComment_; } void submitLine(const std::string& line); void reset() { inComment_ = false; } private: bool inComment_; }; // Class used to parse and return attribute information from a source file class SourceFileAttributesParser : public SourceFileAttributes { public: explicit SourceFileAttributesParser(const std::string& sourceFile, const std::string& packageFile, bool parseDependencies); private: // prohibit copying SourceFileAttributesParser(const SourceFileAttributesParser&); SourceFileAttributesParser& operator=(const SourceFileAttributesParser&); public: // implemetnation of SourceFileAttributes interface virtual const std::string& sourceFile() const { // #nocov return sourceFile_; // #nocov } virtual const_iterator begin() const { return attributes_.begin(); } virtual const_iterator end() const { return attributes_.end(); } virtual const std::vector& modules() const { return modules_; } virtual const std::vector >& roxygenChunks() const { return roxygenChunks_; } virtual bool hasGeneratorOutput() const { return !attributes_.empty() || !modules_.empty() || !roxygenChunks_.empty(); } virtual bool hasInterface(const std::string& name) const { for (const_iterator it=begin(); it != end(); ++it) { if (it->name() == kInterfacesAttribute) { return it->hasParameter(name); // #nocov } } // if there's no interfaces attrbute we default to R if (name == kInterfaceR) return true; else return false; } // Was a package init function found? bool hasPackageInit() const { return hasPackageInit_; } // Get lines of embedded R code const std::vector& embeddedR() const { return embeddedR_; } // Get source dependencies const std::vector& sourceDependencies() const { return sourceDependencies_; }; private: // Parsing helpers Attribute parseAttribute(const std::vector& match, int lineNumber); std::vector parseParameters(const std::string& input); Function parseFunction(size_t lineNumber); std::string parseSignature(size_t lineNumber); std::vector parseArguments(const std::string& argText); Type parseType(const std::string& text); // Validation helpers bool isKnownAttribute(const std::string& name) const; void attributeWarning(const std::string& message, const std::string& attribute, size_t lineNumber); void attributeWarning(const std::string& message, size_t lineNumber); void rcppExportWarning(const std::string& message, size_t lineNumber); void rcppExportNoFunctionFoundWarning(size_t lineNumber); void rcppExportInvalidParameterWarning(const std::string& param, size_t lineNumber); void rcppInterfacesWarning(const std::string& message, size_t lineNumber); private: std::string sourceFile_; CharacterVector lines_; std::vector attributes_; std::vector modules_; bool hasPackageInit_; std::vector embeddedR_; std::vector sourceDependencies_; std::vector > roxygenChunks_; std::vector roxygenBuffer_; }; } // namespace attributes } // namespace Rcpp /******************************************************************* * AttributesGen.h *******************************************************************/ namespace Rcpp { namespace attributes { // Abstract class which manages writing of code for compileAttributes class ExportsGenerator { protected: ExportsGenerator(const std::string& targetFile, const std::string& package, const std::string& commentPrefix); private: // prohibit copying ExportsGenerator(const ExportsGenerator&); ExportsGenerator& operator=(const ExportsGenerator&); public: virtual ~ExportsGenerator() {} // Name of target file and package const std::string& targetFile() const { return targetFile_; } const std::string& package() const { return package_; } const std::string& packageCpp() const { return packageCpp_; } const std::string packageCppPrefix() const { return "_" + packageCpp(); } // Abstract interface for code generation virtual void writeBegin() = 0; void writeFunctions(const SourceFileAttributes& attributes, bool verbose); // see doWriteFunctions below virtual void writeEnd(bool hasPackageInit) = 0; virtual bool commit(const std::vector& includes) = 0; // Remove the generated file entirely bool remove(); // Allow generator to appear as a std::ostream& operator std::ostream&() { return codeStream_; } protected: // Allow access to the output stream std::ostream& ostr() { return codeStream_; } bool hasCppInterface() const { return hasCppInterface_; } // Shared knowledge about function namees std::string exportValidationFunction() { return "RcppExport_validate"; } std::string exportValidationFunctionRegisteredName() { return packageCppPrefix() + "_" + exportValidationFunction(); } std::string registerCCallableExportedName() { // #nocov return packageCppPrefix() + "_RcppExport_registerCCallable"; // #nocov } // Commit the stream -- is a no-op if the existing code is identical // to the generated code. Returns true if data was written and false // if it wasn't (throws exception on io error) bool commit(const std::string& preamble = std::string()); // Convert a dot in package name to underscore for use in header file name std::string dotNameHelper(const std::string & name) const; private: // Private virtual for doWriteFunctions so the base class // can always intercept writeFunctions virtual void doWriteFunctions(const SourceFileAttributes& attributes, bool verbose) = 0; // Check whether it's safe to overwrite this file (i.e. whether we // generated the file in the first place) bool isSafeToOverwrite() const { return existingCode_.empty() || (existingCode_.find(generatorToken()) != std::string::npos); } // UUID that we write into a comment within the file (so that we can // strongly identify that a file was generated by us before overwriting it) std::string generatorToken() const { return "10BE3573-1514-4C36-9D1C-5A225CD40393"; } private: std::string targetFile_; std::string package_; std::string packageCpp_; std::string commentPrefix_; std::string existingCode_; std::ostringstream codeStream_; bool hasCppInterface_; }; // Class which manages generating RcppExports.cpp class CppExportsGenerator : public ExportsGenerator { public: explicit CppExportsGenerator(const std::string& packageDir, const std::string& package, const std::string& fileSep); virtual void writeBegin() {}; virtual void writeEnd(bool hasPackageInit); virtual bool commit(const std::vector& includes); private: virtual void doWriteFunctions(const SourceFileAttributes& attributes, bool verbose); std::string registerCCallable(size_t indent, const std::string& exportedName, const std::string& name) const; private: // for generating calls to init functions std::vector initFunctions_; // for generating C++ interfaces std::vector cppExports_; // for generating Rcpp::export native routine registration std::vector nativeRoutines_; // for generating module native routine registration std::vector modules_; }; // Class which manages generating PackageName_RcppExports.h header file class CppExportsIncludeGenerator : public ExportsGenerator { public: CppExportsIncludeGenerator(const std::string& packageDir, const std::string& package, const std::string& fileSep); virtual void writeBegin(); virtual void writeEnd(bool hasPackageInit); virtual bool commit(const std::vector& includes); private: virtual void doWriteFunctions(const SourceFileAttributes& attributes, bool verbose); std::string getCCallable(const std::string& function) const; std::string getHeaderGuard() const; private: std::string includeDir_; }; // Class which manages generating PackageName.h header file class CppPackageIncludeGenerator : public ExportsGenerator { public: CppPackageIncludeGenerator(const std::string& packageDir, const std::string& package, const std::string& fileSep); virtual void writeBegin() {} virtual void writeEnd(bool hasPackageInit); virtual bool commit(const std::vector& includes); private: virtual void doWriteFunctions(const SourceFileAttributes&, bool) {} std::string getHeaderGuard() const; private: std::string includeDir_; }; // Class which manages generator RcppExports.R class RExportsGenerator : public ExportsGenerator { public: RExportsGenerator(const std::string& packageDir, const std::string& package, bool registration, const std::string& fileSep); virtual void writeBegin() {} virtual void writeEnd(bool hasPackageInit); virtual bool commit(const std::vector& includes); private: virtual void doWriteFunctions(const SourceFileAttributes& attributes, bool verbose); bool registration_; }; // Class to manage and dispatch to a list of generators class ExportsGenerators { public: typedef std::vector::iterator Itr; ExportsGenerators() {} virtual ~ExportsGenerators(); void add(ExportsGenerator* pGenerator); void writeBegin(); void writeFunctions(const SourceFileAttributes& attributes, bool verbose); void writeEnd(bool hasPackageInit); // Commit and return a list of the files that were updated std::vector commit( const std::vector& includes); // Remove and return a list of files that were removed std::vector remove(); private: // prohibit copying ExportsGenerators(const ExportsGenerators&); ExportsGenerators& operator=(const ExportsGenerators&); private: std::vector generators_; }; // Standalone generation helpers (used by sourceCpp) std::string generateRArgList(const Function& function); bool checkRSignature(const Function& function, std::string args); void initializeGlobals(std::ostream& ostr); void generateCpp(std::ostream& ostr, const SourceFileAttributes& attributes, bool includePrototype, bool cppInterface, const std::string& contextId); } // namespace attributes } // namespace Rcpp /******************************************************************* * AttributesParser.cpp *******************************************************************/ namespace Rcpp { namespace attributes { namespace { Rcpp::List regexMatches(Rcpp::CharacterVector lines, const std::string& regex) { Rcpp::Environment base("package:base"); Rcpp::Function regexec = base["regexec"]; Rcpp::Function regmatches = base["regmatches"]; Rcpp::RObject result = regexec(regex, lines); Rcpp::List matches = regmatches(lines, result); return matches; } template void readFile(const std::string& file, Stream& os) { std::ifstream ifs(file.c_str()); if (ifs.fail()) throw Rcpp::file_io_error(file); // #nocov os << ifs.rdbuf(); ifs.close(); } template void readLines(std::istream& is, Collection* pLines) { pLines->clear(); std::string line; while(std::getline(is, line)) { // strip \r (for the case of windows line terminators on posix) if (line.length() > 0 && *line.rbegin() == '\r') line.erase(line.length()-1, 1); stripTrailingLineComments(&line); pLines->push_back(line); } } bool addUniqueDependency(Rcpp::CharacterVector include, std::vector* pDependencies) { // return false if we already have this include std::string path = Rcpp::as(include); for (size_t i = 0; isize(); ++i) { if (pDependencies->at(i).path() == path) return false; } // add it and return true pDependencies->push_back(FileInfo(path)); return true; } void parseSourceDependencies(const std::string& sourceFile, std::vector* pDependencies) { // import R functions Rcpp::Environment baseEnv = Rcpp::Environment::base_env(); Rcpp::Function dirname = baseEnv["dirname"]; Rcpp::Function filepath = baseEnv["file.path"]; Rcpp::Function normalizePath = baseEnv["normalizePath"]; Rcpp::Function fileExists = baseEnv["file.exists"]; Rcpp::Environment toolsEnv = Rcpp::Environment::namespace_env( "tools"); Rcpp::Function filePathSansExt = toolsEnv["file_path_sans_ext"]; // get the path to the source file's directory Rcpp::CharacterVector sourceDir = dirname(sourceFile); // read the source file into a buffer std::stringstream buffer; readFile(sourceFile, buffer); // Now read into a list of strings (which we can pass to regexec) // First read into a std::deque (which will handle lots of append // operations efficiently) then copy into an R chracter vector std::deque lines; readLines(buffer, &lines); Rcpp::CharacterVector linesVector = Rcpp::wrap(lines); // look for local includes Rcpp::List matches = regexMatches( linesVector, "^\\s*#include\\s*\"([^\"]+)\"\\s*$"); // accumulate local includes (skip commented sections) CommentState commentState; std::vector newDependencies; for (int i = 0; i(include))); } std::vector exts; exts.push_back(".cc"); exts.push_back(".cpp"); for (size_t i = 0; i( // #nocov filePathSansExt(include)) + exts[i]; exists = fileExists(file); if (exists[0]) { if (addUniqueDependency(file, pDependencies)) { FileInfo fileInfo(file); newDependencies.push_back(fileInfo); } } } } } } } // look for dependencies recursively for (size_t i = 0; i parseSourceDependencies( std::string sourceFile) { // normalize source file Rcpp::Environment baseEnv = Rcpp::Environment::base_env(); Rcpp::Function normalizePath = baseEnv["normalizePath"]; sourceFile = Rcpp::as(normalizePath(sourceFile, "/")); // parse dependencies std::vector dependencies; parseSourceDependencies(sourceFile, &dependencies); // remove main source file dependencies.erase(std::remove(dependencies.begin(), // #nocov dependencies.end(), FileInfo(sourceFile)), dependencies.end()); return dependencies; } // Parse embedded R code chunks from a file (receives the lines of the // file as a CharcterVector for using with regexec and as a standard // stl vector for traversal/insepection) std::vector parseEmbeddedR( Rcpp::CharacterVector linesVector, const std::deque& lines) { Rcpp::List matches = regexMatches(linesVector, "^\\s*/\\*{3,}\\s*[Rr]\\s*$"); bool withinRBlock = false; CommentState commentState; std::vector embeddedR; for (int i = 0; i 0; // check state and do the right thing if (beginRBlock) { withinRBlock = true; // #nocov } else if (withinRBlock) { if (commentState.inComment()) // #nocov start embeddedR.push_back(line); else withinRBlock = false; // #nocov end } } return embeddedR; } } // anonymous namespace // Generate a type signature for the function with the provided name // (type signature == function pointer declaration) std::string Function::signature(const std::string& name) const { // #nocov start std::ostringstream ostr; ostr << type() << "(*" << name << ")("; const std::vector& args = arguments(); for (std::size_t i = 0; i::const_iterator it = params_.begin(); it != params_.end(); ++it) { if (it->name() == name) // #nocov return *it; // #nocov } return Param(); } // Type operator << std::ostream& operator<<(std::ostream& os, const Type& type) { if (!type.empty()) { if (type.isConst()) os << "const "; os << type.name(); if (type.isReference()) os << "&"; } return os; } // Print argument void printArgument(std::ostream& os, const Argument& argument, bool printDefault = true) { if (!argument.empty()) { os << argument.type(); if (!argument.name().empty()) { os << " "; os << argument.name(); if (printDefault && !argument.defaultValue().empty()) os << " = " << argument.defaultValue(); // #nocov } } } // Argument operator << std::ostream& operator<<(std::ostream& os, const Argument& argument) {// #nocov start printArgument(os, argument); return os; // #nocov end } // Print function void printFunction(std::ostream& os, const Function& function, bool printArgDefaults = true) { if (!function.empty()) { if (!function.type().empty()) { os << function.type(); os << " "; } os << function.name(); os << "("; const std::vector& arguments = function.arguments(); for (std::size_t i = 0; i& params = attribute.params(); if (params.size() > 0) { os << "("; for (std::size_t i = 0; i lines; readLines(buffer, &lines); lines_ = Rcpp::wrap(lines); // Scan for attributes CommentState commentState; Rcpp::List matches = regexMatches(lines_, "^\\s*//\\s*\\[\\[Rcpp::(\\w+)(\\(.*?\\))?\\]\\]\\s*$"); for (int i = 0; i 0) { // if the match size isn't 3 then regmatches has not behaved // as expected (it should return a vector of either 0 or 3 // elements). we don't ever expect this to occur but if it // does let's not crash if (match.size() != 3) continue; // #nocov // add the attribute Attribute attr = parseAttribute( Rcpp::as >(match), i); attributes_.push_back(attr); } // if it's not an attribute line then it could still be a // line of interest (e.g. roxygen comment) else { // save roxygen comments if (line.find("//'") == 0) { std::string roxLine = "#" + line.substr(2); roxygenBuffer_.push_back(roxLine); } // a non-roxygen line causes us to clear the roxygen buffer else if (!roxygenBuffer_.empty()) { roxygenChunks_.push_back(roxygenBuffer_); // #nocov roxygenBuffer_.clear(); // #nocov } } } // Scan for Rcpp modules commentState.reset(); Rcpp::List modMatches = regexMatches(lines_, "^\\s*RCPP_MODULE\\s*\\(\\s*(\\w+)\\s*\\).*$"); for (int i = 0; i 0) { const char * name = match[1]; modules_.push_back(name); } } // Scan for package init function hasPackageInit_ = false; commentState.reset(); std::string pkgInit = "R_init_" + packageNameCpp; Rcpp::List initMatches = regexMatches(lines_, "^[^/]+" + pkgInit + ".*DllInfo.*$"); for (int i = 0; i 0) { hasPackageInit_ = true; // #nocov start break; } // #nocov end } // Parse embedded R embeddedR_ = parseEmbeddedR(lines_, lines); // Recursively parse dependencies if requested if (parseDependencies) { // get source dependencies sourceDependencies_ = parseSourceDependencies(sourceFile); // parse attributes and modules from each dependent file for (size_t i = 0; i& match, int lineNumber) { // Attribute name std::string name = match[1]; // Warn if this is an unknown attribute if (!isKnownAttribute(name)) { attributeWarning("Unrecognized attribute Rcpp::" + name, // #nocov lineNumber); // #nocov } // Extract params if we've got them std::vector params; std::string paramsText = match[2]; if (!paramsText.empty()) { // we know from the regex that it's enclosed in parens so remove // trim before we do this just in case someone updates the regex // to allow for whitespace around the call trimWhitespace(¶msText); paramsText = paramsText.substr(1, paramsText.size()-2); // parse the parameters params = parseParameters(paramsText); } // Extract function signature if this is a function attribute // and it doesn't appear at the end of the file Function function; // special handling for export and init if (name == kExportAttribute || name == kInitAttribute) { // parse the function (unless we are at the end of the file in // which case we print a warning) if ((lineNumber + 1) < lines_.size()) function = parseFunction(lineNumber + 1); else rcppExportWarning("No function found", lineNumber); // #nocov // validate parameters for (std::size_t i=0; i 0)) { rcppExportWarning("No value specified for parameter '" + name + "'", lineNumber); } // parameter that isn't name or rng else if (!value.empty() && (name != kExportName) && (name != kExportRng) && (name != kExportInvisible) && (name != kExportSignature)) { rcppExportWarning("Unrecognized parameter '" + name + "'", lineNumber); } // rng that isn't true or false else if (name == kExportRng) { if (value != kParamValueFalse && value != kParamValueTrue && value != kParamValueFALSE && value != kParamValueTRUE) { rcppExportWarning("rng value must be true or false", lineNumber); } } // invisible that isn't true of false else if (name == kExportInvisible) { if (value != kParamValueFalse && value != kParamValueTrue && value != kParamValueFALSE && value != kParamValueTRUE) { rcppExportWarning("invisible value must be true or false", lineNumber); // #nocov end } } } } // validate interfaces parameter else if (name == kInterfacesAttribute) { if (params.empty()) { // #nocov start rcppInterfacesWarning("No interfaces specified", lineNumber);// } else { for (std::size_t i=0; i SourceFileAttributesParser::parseParameters( const std::string& input) { std::string::size_type blockstart = input.find_first_of(kParamBlockStart); std::string::size_type blockend = input.find_last_of(kParamBlockEnd); const std::string delimiters(","); std::vector params; std::string::size_type current; std::string::size_type next = std::string::npos; std::string::size_type signature_param_start = std::string::npos; do { // #nocov next = input.find_first_not_of(delimiters, next + 1); if (next == std::string::npos) break; // #nocov current = next; do { next = input.find_first_of(delimiters, next + 1); } while((next >= blockstart) && (next <= blockend) && (next != std::string::npos)); params.push_back(Param(input.substr(current, next - current))); if(params.back().name() == kExportSignature) { signature_param_start = current; } } while(next != std::string::npos); // if the signature param was found, then check that the name, // start block and end block exist and are in the correct order if(signature_param_start != std::string::npos) { bool sigchecks = signature_param_start < blockstart && blockstart < blockend && blockstart != std::string::npos && blockend != std::string::npos; if(!sigchecks) { throw Rcpp::exception("signature parameter found but missing {}"); } } return params; } // Parse a function from the specified spot in the source file Function SourceFileAttributesParser::parseFunction(size_t lineNumber) { // Establish the text to parse for the signature std::string signature = parseSignature(lineNumber); if (signature.empty()) { rcppExportNoFunctionFoundWarning(lineNumber); // #nocov return Function(); // #nocov } // Start at the end and look for the () that deliniates the arguments // (bail with an empty result if we can't find them) std::string::size_type endParenLoc = signature.find_last_of(')'); std::string::size_type beginParenLoc = signature.find_first_of('('); if (endParenLoc == std::string::npos || beginParenLoc == std::string::npos || endParenLoc < beginParenLoc) { rcppExportNoFunctionFoundWarning(lineNumber); // #nocov return Function(); // #nocov } // Find the type and name by scanning backwards for the whitespace that // delimites the type and name Type type; std::string name; const std::string preambleText = signature.substr(0, beginParenLoc); for (std::string::const_reverse_iterator it = preambleText.rbegin(); it != preambleText.rend(); ++it) { char ch = *it; if (isWhitespace(ch)) { if (!name.empty()) { // we are at the break between type and name so we can also // extract the type std::string typeText; while (++it != preambleText.rend()) typeText.insert(0U, 1U, *it); type = parseType(typeText); // break (since we now have the name and the type) break; } else continue; // #nocov } else { name.insert(0U, 1U, ch); } } // If we didn't find a name then bail if (name.empty()) { rcppExportNoFunctionFoundWarning(lineNumber); // #nocov return Function(); // #nocov } // If we didn't find a type then bail if (type.empty()) { // #nocov start rcppExportWarning("No function return type found", lineNumber); return Function(); // #nocov end } // Now scan for arguments std::vector arguments; std::string argsText = signature.substr(beginParenLoc + 1, endParenLoc-beginParenLoc-1); std::vector args = parseArguments(argsText); for (std::vector::const_iterator it = args.begin(); it != args.end(); ++it) { // Get argument sans whitespace (bail if the arg is empty) std::string arg = *it; trimWhitespace(&arg); if (arg.empty()) { // we don't warn here because the compilation will fail anyway continue; // #nocov } // If the argument has an = within it then it has a default value std::string defaultValue; std::string::size_type eqPos = arg.find_first_of('='); if ( (eqPos != std::string::npos) && ((eqPos + 1) < arg.size()) ) { defaultValue = arg.substr(eqPos+1); trimWhitespace(&defaultValue); arg = arg.substr(0, eqPos); trimWhitespace(&arg); } // Scan backwards for whitespace to determine where the type ends // (we go backwards because whitespace is valid inside the type // identifier but invalid inside the variable name). Note that if // there is no whitespace we'll end up taking the whole string, // which allows us to capture a type with no variable (but note // we'll ultimately fail to parse types with no variable if they // have embedded whitespace) std::string::size_type pos = arg.find_last_of(kWhitespaceChars); // check for name std::string name; if (pos != std::string::npos) { // insert whitespace if variables are joint with '&' std::string::size_type ref_pos = arg.substr(pos).find_last_of("&"); if (ref_pos != std::string::npos) { pos += ref_pos + 1; // #nocov arg.insert(pos, " "); // #nocov } name = arg.substr(pos); trimWhitespace(&name); } if (name.empty()) { // #nocov start rcppExportInvalidParameterWarning(arg, lineNumber); return Function(); // #nocov end } // check for type string Type type = parseType(arg.substr(0, pos)); if (type.empty()) { // #nocov start rcppExportInvalidParameterWarning(arg, lineNumber); return Function(); // #nocov end } // add argument arguments.push_back(Argument(name, type, defaultValue)); } return Function(type, name, arguments); } // Parse the text of a function signature from the specified line std::string SourceFileAttributesParser::parseSignature(size_t lineNumber) { // Look for the signature termination ({ or ; not inside quotes) // on this line and then subsequent lines if necessary std::string signature; for (size_t i = lineNumber; i < (size_t)lines_.size(); i++) { std::string line; line = lines_[i]; bool insideQuotes = false; char prevChar = 0; // scan for { or ; not inside quotes for (size_t c = 0; c < line.length(); ++c) { // alias character char ch = line.at(c); // update quotes state if (ch == '"' && prevChar != '\\') insideQuotes = !insideQuotes; // found signature termination, append and return if (!insideQuotes && ((ch == '{') || (ch == ';'))) { signature.append(line.substr(0, c)); return signature; } // record prev char (used to check for escaped quote i.e. \") prevChar = ch; } // if we didn't find a terminator on this line then just append the line // and move on to the next line signature.append(line); signature.push_back(' '); } // Not found return std::string(); // #nocov } // Parse arguments from function signature. This is tricky because commas // are used to delimit arguments but are also valid inside template type // qualifiers. std::vector SourceFileAttributesParser::parseArguments( const std::string& argText) { int templateCount = 0; int parenCount = 0; std::string currentArg; std::vector args; char quote = 0; bool escaped = false; typedef std::string::const_iterator it_t; for (it_t it = argText.begin(); it != argText.end(); ++it) { // Store current character char ch = *it; // Ignore quoted strings and character values in single quotes if ( ! quote && (ch == '"' || ch == '\'')) quote = ch; else if (quote && ch == quote && ! escaped) quote = 0; // Escaped character inside quotes if (escaped) escaped = false; else if (quote && ch == '\\') escaped = true; // Detect end of argument declaration if ( ! quote && (ch == ',') && (templateCount == 0) && (parenCount == 0)) { args.push_back(currentArg); currentArg.clear(); continue; } // Append current character if not a space at start if ( ! currentArg.empty() || ch != ' ') currentArg.push_back(ch); // Count use of potentially enclosed brackets if ( ! quote) { switch(ch) { case '<': templateCount++; break; case '>': templateCount--; break; case '(': // #nocov start parenCount++; break; case ')': parenCount--; break; // #nocov end } } } if (!currentArg.empty()) args.push_back(currentArg); return args; } Type SourceFileAttributesParser::parseType(const std::string& text) { const std::string constQualifier("const"); const std::string referenceQualifier("&"); // trim whitespace std::string type = text; trimWhitespace(&type); // check for const and reference bool isConst = false; bool isReference = false; if (type.find(constQualifier) == 0) { isConst = true; type.erase(0, constQualifier.length()); } // if the type is now empty (because it was detected as only const) // then this is an invalid state so we bail if (type.empty()) return Type(); // #nocov if (type.find(referenceQualifier) == (type.length() - referenceQualifier.length())) { isReference = true; type.erase(type.length() - referenceQualifier.length()); } trimWhitespace(&type); // if the type is now empty because of some strange parse then bail if (type.empty()) return Type(); // #nocov return Type(type, isConst, isReference); } // Validation helpers bool SourceFileAttributesParser::isKnownAttribute(const std::string& name) const { return name == kExportAttribute || name == kInitAttribute || name == kDependsAttribute || name == kPluginsAttribute || name == kInterfacesAttribute; } // Print an attribute parsing related warning void SourceFileAttributesParser::attributeWarning( // #nocov start const std::string& message, const std::string& attribute, size_t lineNumber) { // get basename of source file for warning message Rcpp::Function basename = Rcpp::Environment::base_env()["basename"]; std::string file = Rcpp::as(basename(sourceFile_)); std::ostringstream ostr; ostr << message; if (!attribute.empty()) ostr << " for " << attribute << " attribute"; ostr << " at " << file << ":" << lineNumber; showWarning(ostr.str()); } void SourceFileAttributesParser::attributeWarning( const std::string& message, size_t lineNumber) { attributeWarning(message, "", lineNumber); } void SourceFileAttributesParser::rcppExportWarning( const std::string& message, size_t lineNumber) { attributeWarning(message, "Rcpp::export", lineNumber); } void SourceFileAttributesParser::rcppExportNoFunctionFoundWarning( size_t lineNumber) { rcppExportWarning("No function found", lineNumber); } void SourceFileAttributesParser::rcppExportInvalidParameterWarning( const std::string& param, size_t lineNumber) { rcppExportWarning("Invalid parameter: " "'" + param + "'", lineNumber); } void SourceFileAttributesParser::rcppInterfacesWarning( const std::string& message, size_t lineNumber) { attributeWarning(message + " (valid interfaces are 'r' and 'cpp')", "Rcpp::interfaces", lineNumber); } // #nocov end // Track /* */ comment state void CommentState::submitLine(const std::string& line) { std::size_t pos = 0; while (pos != std::string::npos) { // check for a // which would invalidate any other token found std::size_t lineCommentPos = line.find("//", pos); // look for the next token std::string token = inComment() ? "*/" : "/*"; pos = line.find(token, pos); // process the comment token if found if (pos != std::string::npos) { // break if the line comment precedes the comment token if (lineCommentPos != std::string::npos && lineCommentPos < pos) break; // #nocov inComment_ = !inComment_; pos += token.size(); } } } } // namespace attributes } // namespace Rcpp /******************************************************************* * AttributesGen.cpp *******************************************************************/ namespace Rcpp { namespace attributes { // constants namespace { const char * const kRcppExportsSuffix = "_RcppExports.h"; const char * const kTrySuffix = "_try"; } ExportsGenerator::ExportsGenerator(const std::string& targetFile, const std::string& package, const std::string& commentPrefix) : targetFile_(targetFile), package_(package), packageCpp_(package), commentPrefix_(commentPrefix), hasCppInterface_(false) { // read the existing target file if it exists if (FileInfo(targetFile_).exists()) { std::ifstream ifs(targetFile_.c_str()); // #nocov start if (ifs.fail()) throw Rcpp::file_io_error(targetFile_); std::stringstream buffer; buffer << ifs.rdbuf(); existingCode_ = buffer.str(); // #nocov end } std::replace(packageCpp_.begin(), packageCpp_.end(), '.', '_'); // see if this is safe to overwite and throw if it isn't if (!isSafeToOverwrite()) throw Rcpp::file_exists(targetFile_); // #nocov } void ExportsGenerator::writeFunctions( const SourceFileAttributes& attributes, bool verbose) { if (attributes.hasInterface(kInterfaceCpp)) hasCppInterface_ = true; // #nocov doWriteFunctions(attributes, verbose); } // Commit the stream -- is a no-op if the existing code is identical // to the generated code. Returns true if data was written and false // if it wasn't (throws exception on io error) bool ExportsGenerator::commit(const std::string& preamble) { // get the generated code std::string code = codeStream_.str(); // if there is no generated code AND the exports file does not // currently exist then do nothing if (code.empty() && !FileInfo(targetFile_).exists()) return false; // #nocov // write header/preamble std::ostringstream headerStream; headerStream << commentPrefix_ << " Generated by using " << "Rcpp::compileAttributes()" << " -> do not edit by hand" << std::endl; headerStream << commentPrefix_ << " Generator token: " << generatorToken() << std::endl << std::endl; if (!preamble.empty()) headerStream << preamble; // get generated code and only write it if there was a change std::string generatedCode = headerStream.str() + code; if (generatedCode != existingCode_) { // open the file std::ofstream ofs(targetFile_.c_str(), std::ofstream::out | std::ofstream::trunc); if (ofs.fail()) throw Rcpp::file_io_error(targetFile_); // #nocov // write generated code and return ofs << generatedCode; ofs.close(); return true; } else { return false; // #nocov } } // Remove the generated file entirely bool ExportsGenerator::remove() { return removeFile(targetFile_); } // Convert a possible dot in package name to underscore as needed for header file std::string ExportsGenerator::dotNameHelper(const std::string & name) const { std::string newname(name); std::replace(newname.begin(), newname.end(), '.', '_'); return newname; } CppExportsGenerator::CppExportsGenerator(const std::string& packageDir, const std::string& package, const std::string& fileSep) : ExportsGenerator( packageDir + fileSep + "src" + fileSep + "RcppExports.cpp", package, "//") { } void CppExportsGenerator::doWriteFunctions( const SourceFileAttributes& attributes, bool verbose) { // generate functions generateCpp(ostr(), attributes, true, attributes.hasInterface(kInterfaceCpp), packageCppPrefix()); // track cppExports, signatures, and native routines (we use these // at the end to generate the ValidateSignature and RegisterCCallable // functions, and to generate a package init function with native // routine registration) for (SourceFileAttributes::const_iterator // #nocov start it = attributes.begin(); it != attributes.end(); ++it) { if (it->isExportedFunction()) { // add it to the cpp exports list if we are generating // a C++ interface and it's not hidden if (attributes.hasInterface(kInterfaceCpp)) { Function fun = it->function().renamedTo(it->exportedCppName()); if (!fun.isHidden()) cppExports_.push_back(*it); } // add it to the native routines list nativeRoutines_.push_back(*it); } else if (it->name() == kInitAttribute) { initFunctions_.push_back(*it); } } // #nocov end // record modules const std::vector& modules = attributes.modules(); modules_.insert(modules_.end(), modules.begin(), modules.end()); // verbose if requested if (verbose) { // #nocov start Rcpp::Rcout << "Exports from " << attributes.sourceFile() << ":" << std::endl; for (std::vector::const_iterator it = attributes.begin(); it != attributes.end(); ++it) { if (it->isExportedFunction()) Rcpp::Rcout << " " << it->function() << std::endl; } Rcpp::Rcout << std::endl; // #nocov end } } void CppExportsGenerator::writeEnd(bool hasPackageInit) { // generate a function that can be used to validate exported // functions and their signatures prior to looking up with // GetCppCallable (otherwise inconsistent signatures between // client and library would cause a crash) if (hasCppInterface()) { ostr() << std::endl; // #nocov start ostr() << "// validate" << " (ensure exported C++ functions exist before " << "calling them)" << std::endl; ostr() << "static int " << exportValidationFunctionRegisteredName() << "(const char* sig) { " << std::endl; ostr() << " static std::set signatures;" << std::endl; ostr() << " if (signatures.empty()) {" << std::endl; for (std::size_t i=0;i routineNames; std::vector routineArgs; for (std::size_t i=0;i declarations = extraRoutines["declarations"]; std::vector callEntries = extraRoutines["call_entries"]; // add declarations for modules for (std::size_t i=0;i 0) { ostr() << std::endl; for (std::size_t i = 0; i 0) { for (std::size_t i = 0; i& includes) { // includes std::ostringstream ostr; if (!includes.empty()) { for (std::size_t i=0;i" << std::endl; // #nocov ostr << "#include " << std::endl; // #nocov } ostr << std::endl; // always bring in Rcpp ostr << "using namespace Rcpp;" << std::endl << std::endl; // initialize references to global Rostreams initializeGlobals(ostr); // commit with preamble return ExportsGenerator::commit(ostr.str()); } CppExportsIncludeGenerator::CppExportsIncludeGenerator( const std::string& packageDir, const std::string& package, const std::string& fileSep) : ExportsGenerator( packageDir + fileSep + "inst" + fileSep + "include" + fileSep + dotNameHelper(package) + kRcppExportsSuffix, package, "//") { includeDir_ = packageDir + fileSep + "inst" + fileSep + "include"; } void CppExportsIncludeGenerator::writeBegin() { ostr() << "namespace " << packageCpp() << " {" << std::endl << std::endl; // Import Rcpp into this namespace. This allows declarations to // be written without fully qualifying all Rcpp types. The only // negative side-effect is that when this package's namespace // is imported it will also pull in Rcpp. However since this is // opt-in and represents a general desire to do namespace aliasing // this seems okay ostr() << " using namespace Rcpp;" << std::endl << std::endl; // Write our export validation helper function. Putting it in // an anonymous namespace will hide it from callers and give // it per-translation unit linkage ostr() << " namespace {" << std::endl; ostr() << " void validateSignature(const char* sig) {" << std::endl; ostr() << " Rcpp::Function require = " << "Rcpp::Environment::base_env()[\"require\"];" << std::endl; ostr() << " require(\"" << package() << "\", " << "Rcpp::Named(\"quietly\") = true);" << std::endl; std::string validate = "validate"; std::string fnType = "Ptr_" + validate; ostr() << " typedef int(*" << fnType << ")(const char*);" << std::endl; std::string ptrName = "p_" + validate; ostr() << " static " << fnType << " " << ptrName << " = " << "(" << fnType << ")" << std::endl << " " << getCCallable(exportValidationFunctionRegisteredName()) << ";" << std::endl; ostr() << " if (!" << ptrName << "(sig)) {" << std::endl; ostr() << " throw Rcpp::function_not_exported(" << std::endl << " " << "\"C++ function with signature '\" + std::string(sig) + \"' not found in " << package() << "\");" << std::endl; ostr() << " }" << std::endl; ostr() << " }" << std::endl; ostr() << " }" << std::endl << std::endl; } void CppExportsIncludeGenerator::doWriteFunctions( const SourceFileAttributes& attributes, bool) { // don't write anything if there is no C++ interface if (!attributes.hasInterface(kInterfaceCpp)) return; for(std::vector::const_iterator // #nocov start it = attributes.begin(); it != attributes.end(); ++it) { if (it->isExportedFunction()) { Function function = it->function().renamedTo(it->exportedCppName()); // if it's hidden then don't generate a C++ interface if (function.isHidden()) continue; ostr() << " inline " << function << " {" << std::endl; std::string fnType = "Ptr_" + function.name(); ostr() << " typedef SEXP(*" << fnType << ")("; for (size_t i=0; irng()) ostr() << " RNGScope RCPP_rngScope_gen;" << std::endl; ostr() << " rcpp_result_gen = " << ptrName << "("; const std::vector& args = function.arguments(); for (std::size_t i = 0; i(Rcpp::wrap(" << args[i].name() << "))"; if (i != (args.size()-1)) ostr() << ", "; } ostr() << ");" << std::endl; ostr() << " }" << std::endl; ostr() << " if (rcpp_result_gen.inherits(\"interrupted-error\"))" << std::endl << " throw Rcpp::internal::InterruptedException();" << std::endl; ostr() << " if (Rcpp::internal::isLongjumpSentinel(rcpp_result_gen))" << std::endl << " throw Rcpp::LongjumpException(rcpp_result_gen);" << std::endl; ostr() << " if (rcpp_result_gen.inherits(\"try-error\"))" << std::endl << " throw Rcpp::exception(Rcpp::as(" << "rcpp_result_gen).c_str());" << std::endl; if (!function.type().isVoid()) { ostr() << " return Rcpp::as<" << function.type() << " >" << "(rcpp_result_gen);" << std::endl; } ostr() << " }" << std::endl << std::endl; // #nocov end } } } void CppExportsIncludeGenerator::writeEnd(bool) { ostr() << "}" << std::endl; ostr() << std::endl; ostr() << "#endif // " << getHeaderGuard() << std::endl; } bool CppExportsIncludeGenerator::commit( const std::vector& includes) { if (hasCppInterface()) { // create the include dir if necessary createDirectory(includeDir_); // #nocov start // generate preamble std::ostringstream ostr; // header guard std::string guard = getHeaderGuard(); ostr << "#ifndef " << guard << std::endl; ostr << "#define " << guard << std::endl << std::endl; // includes if (!includes.empty()) { for (std::size_t i=0;i&) { if (hasCppInterface()) { // create the include dir if necessary createDirectory(includeDir_); // #nocov // commit return ExportsGenerator::commit(); // #nocov } else { return ExportsGenerator::remove(); } } std::string CppPackageIncludeGenerator::getHeaderGuard() const { // #nocov return "RCPP_" + packageCpp() + "_H_GEN_"; // #nocov } RExportsGenerator::RExportsGenerator(const std::string& packageDir, const std::string& package, bool registration, const std::string& fileSep) : ExportsGenerator( packageDir + fileSep + "R" + fileSep + "RcppExports.R", package, "#"), registration_(registration) { } void RExportsGenerator::doWriteFunctions( const SourceFileAttributes& attributes, bool) { // write standalone roxygen chunks const std::vector >& roxygenChunks = attributes.roxygenChunks(); for (std::size_t i = 0; i& chunk = roxygenChunks[i]; // #nocov start for (std::size_t l = 0; l < chunk.size(); l++) ostr() << chunk[l] << std::endl; ostr() << "NULL" << std::endl << std::endl; // #nocov end } // write exported functions if (attributes.hasInterface(kInterfaceR)) { // process each attribute for(std::vector::const_iterator it = attributes.begin(); it != attributes.end(); ++it) { // alias the attribute and function (bail if not export) const Attribute& attribute = *it; if (!attribute.isExportedFunction()) continue; // #nocov const Function& function = attribute.function(); // print roxygen lines for (size_t i=0; i& arguments = function.arguments(); for (size_t i = 0; i&) { return ExportsGenerator::commit(); } ExportsGenerators::~ExportsGenerators() { try { for(Itr it = generators_.begin(); it != generators_.end(); ++it) delete *it; generators_.clear(); } catch(...) {} } void ExportsGenerators::add(ExportsGenerator* pGenerator) { generators_.push_back(pGenerator); } void ExportsGenerators::writeBegin() { for(Itr it = generators_.begin(); it != generators_.end(); ++it) (*it)->writeBegin(); } void ExportsGenerators::writeFunctions( const SourceFileAttributes& attributes, bool verbose) { for(Itr it = generators_.begin(); it != generators_.end(); ++it) (*it)->writeFunctions(attributes, verbose); } void ExportsGenerators::writeEnd(bool hasPackageInit) { for(Itr it = generators_.begin(); it != generators_.end(); ++it) (*it)->writeEnd(hasPackageInit); } // Commit and return a list of the files that were updated std::vector ExportsGenerators::commit( const std::vector& includes) { std::vector updated; for(Itr it = generators_.begin(); it != generators_.end(); ++it) { if ((*it)->commit(includes)) updated.push_back((*it)->targetFile()); } return updated; } // Remove and return a list of files that were removed std::vector ExportsGenerators::remove() { // #nocov start std::vector removed; for(Itr it = generators_.begin(); it != generators_.end(); ++it) { if ((*it)->remove()) removed.push_back((*it)->targetFile()); } return removed; } // Helpers for converting C++ default arguments to R default arguments namespace { // convert a C++ numeric argument to an R argument value // (returns empty string if no conversion is possible) std::string cppNumericArgToRArg(const std::string& type, const std::string& cppArg) { // check for a number double num; std::stringstream argStream(cppArg); if ((argStream >> num)) { // L suffix means return the value literally if (!argStream.eof()) { std::string suffix; argStream >> suffix; if (argStream.eof() && suffix == "L") return cppArg; } // no decimal and the type isn't explicitly double or // float means integer if (cppArg.find('.') == std::string::npos && type != "double" && type != "float") return cppArg + "L"; // otherwise return arg literally else return cppArg; } else { return std::string(); } } // convert a C++ ::create style argument value to an R argument // value (returns empty string if no conversion is possible) std::string cppCreateArgToRArg(const std::string& cppArg) { std::string create = "::create"; size_t createLoc = cppArg.find(create); if (createLoc == std::string::npos || ((createLoc + create.length()) >= cppArg.size())) { return std::string(); } std::string type = cppArg.substr(0, createLoc); std::string rcppScope = "Rcpp::"; size_t rcppLoc = type.find(rcppScope); if (rcppLoc == 0 && type.size() > rcppScope.length()) type = type.substr(rcppScope.length()); std::string args = cppArg.substr(createLoc + create.length()); if (type == "CharacterVector") return "as.character( c" + args + ")"; if (type == "IntegerVector") return "as.integer( c" + args + ")"; if (type == "NumericVector") return "as.numeric( c" + args + ")"; if (type == "LogicalVector") return "as.logical( c" + args + ")"; return std::string(); } // convert a C++ Matrix to an R argument (returns empty string // if no conversion possible) std::string cppMatrixArgToRArg(const std::string& cppArg) { // look for Matrix std::string matrix = "Matrix"; size_t matrixLoc = cppArg.find(matrix); if (matrixLoc == std::string::npos || ((matrixLoc + matrix.length()) >= cppArg.size())) { return std::string(); } std::string args = cppArg.substr(matrixLoc + matrix.length()); return "matrix" + args; // #nocov end } // convert a C++ literal to an R argument (returns empty string // if no conversion possible) std::string cppLiteralArgToRArg(const std::string& cppArg) { if (cppArg == "true") return "TRUE"; else if (cppArg == "false") return "FALSE"; else if (cppArg == "R_NilValue") return "NULL"; else if (cppArg == "NA_STRING") // #nocov start return "NA_character_"; else if (cppArg == "NA_INTEGER") return "NA_integer_"; else if (cppArg == "NA_LOGICAL") return "NA_integer_"; else if (cppArg == "NA_REAL") return "NA_real_"; else return std::string(); } // convert an Rcpp container constructor to an R argument // (returns empty string if no conversion possible) std::string cppConstructorArgToRArg(const std::string& cppArg) { // map Rcpp containers to R default initializers static std::map RcppContainerToR; RcppContainerToR.insert(std::make_pair("NumericVector", "numeric")); RcppContainerToR.insert(std::make_pair("DoubleVector", "numeric")); RcppContainerToR.insert(std::make_pair("CharacterVector", "character")); RcppContainerToR.insert(std::make_pair("IntegerVector", "integer")); RcppContainerToR.insert(std::make_pair("LogicalVector", "logical")); RcppContainerToR.insert(std::make_pair("ComplexVector", "complex")); // for each entry in the map above, see if we find it; if we do, // return the R version typedef std::map::const_iterator Iterator; for (Iterator it = RcppContainerToR.begin(); it != RcppContainerToR.end(); ++it) { size_t loc = cppArg.find(it->first); if (loc != std::string::npos) { return it->second + cppArg.substr(it->first.size(), std::string::npos); } } return std::string(); // #nocov end } // convert a C++ argument value to an R argument value (returns empty // string if no conversion is possible) std::string cppArgToRArg(const std::string& type, const std::string& cppArg) { // try for quoted string if (isQuoted(cppArg)) return cppArg; // try for literal std::string rArg = cppLiteralArgToRArg(cppArg); if (!rArg.empty()) return rArg; // try for a create arg rArg = cppCreateArgToRArg(cppArg); // #nocov start if (!rArg.empty()) return rArg; // try for a matrix arg rArg = cppMatrixArgToRArg(cppArg); if (!rArg.empty()) return rArg; // try for a numeric arg rArg = cppNumericArgToRArg(type, cppArg); if (!rArg.empty()) return rArg; // try for a constructor arg rArg = cppConstructorArgToRArg(cppArg); if (!rArg.empty()) return rArg; // couldn't parse the arg return std::string(); // #nocov end } } // anonymous namespace // Generate an R argument list for a function std::string generateRArgList(const Function& function) { std::ostringstream argsOstr; const std::vector& arguments = function.arguments(); for (size_t i = 0; i required_args; const std::vector& arguments = function.arguments(); for (size_t i = 0; i parsed_args = Rcpp::as >(pargs_cv); for(size_t i=0; i& Rcpp::Rcout = Rcpp::Rcpp_cout_get();"; ostr << std::endl; ostr << "Rcpp::Rostream& Rcpp::Rcerr = Rcpp::Rcpp_cerr_get();"; ostr << std::endl; ostr << "#endif" << std::endl << std::endl; } // Generate the C++ code required to make [[Rcpp::export]] functions // available as C symbols with SEXP parameters and return void generateCpp(std::ostream& ostr, const SourceFileAttributes& attributes, bool includePrototype, bool cppInterface, const std::string& contextId) { // process each attribute for(std::vector::const_iterator it = attributes.begin(); it != attributes.end(); ++it) { // alias the attribute and function (bail if not export) const Attribute& attribute = *it; if (!attribute.isExportedFunction()) continue; const Function& function = attribute.function(); // include prototype if requested if (includePrototype) { ostr << "// " << function.name() << std::endl; printFunction(ostr, function, false); ostr << ";"; } // write the C++ callable SEXP-based function (this version // returns errors via "try-error") ostr << std::endl; ostr << (cppInterface ? "static" : "RcppExport"); ostr << " SEXP "; std::string funcName = contextId + "_" + function.name(); ostr << funcName; if (cppInterface) ostr << kTrySuffix; // #nocov ostr << "("; std::ostringstream ostrArgs; const std::vector& arguments = function.arguments(); for (size_t i = 0; i::type " << argument.name() << "(" << argument.name() << "SEXP);" << std::endl; } ostr << " "; if (!function.type().isVoid()) ostr << "rcpp_result_gen = Rcpp::wrap("; ostr << function.name() << "("; for (size_t i = 0; i(buffer.st_mtime); } } // Remove a file (call back into R for this) bool removeFile(const std::string& path) { if (FileInfo(path).exists()) { Rcpp::Function rm = Rcpp::Environment::base_env()["file.remove"]; // #nocov start rm(path); return true; // #nocov end } else { return false; } } // Recursively create a directory (call back into R for this) void createDirectory(const std::string& path) { // #nocov start if (!FileInfo(path).exists()) { Rcpp::Function mkdir = Rcpp::Environment::base_env()["dir.create"]; mkdir(path, Rcpp::Named("recursive") = true); } } // #nocov end // Known whitespace chars const char * const kWhitespaceChars = " \f\n\r\t\v"; // Query whether a character is whitespace bool isWhitespace(char ch) { return std::strchr(kWhitespaceChars, ch) != NULL; } // Remove trailing line comments -- ie, find comments that don't begin // a line, and remove them. We avoid stripping attributes. void stripTrailingLineComments(std::string* pStr) { if (pStr->empty()) return; size_t len = pStr->length(); bool inString = false; size_t idx = 0; // if this is an roxygen comment, then bail if (isRoxygenCpp(*pStr)) return; // skip over initial whitespace idx = pStr->find_first_not_of(kWhitespaceChars); if (idx == std::string::npos) return; // skip over a first comment if (idx + 1 < len && pStr->at(idx) == '/' && pStr->at(idx + 1) == '/') { idx = idx + 2; } // since we are searching for "//", we iterate up to 2nd last character while (idx < len - 1) { if (inString) { if (pStr->at(idx) == '"' && pStr->at(idx - 1) != '\\') { inString = false; } } else { if (pStr->at(idx) == '"') { inString = true; } } if (!inString && pStr->at(idx) == '/' && pStr->at(idx + 1) == '/') { pStr->erase(idx); return; } ++idx; } } // Trim a string void trimWhitespace(std::string* pStr) { // skip empty case if (pStr->empty()) return; // #nocov // trim right std::string::size_type pos = pStr->find_last_not_of(kWhitespaceChars); if (pos != std::string::npos) pStr->erase(pos + 1); // trim left pos = pStr->find_first_not_of(kWhitespaceChars); pStr->erase(0, pos); } // Strip balanced quotes from around a string (assumes already trimmed) void stripQuotes(std::string* pStr) { if (pStr->length() < 2) return; char quote = *(pStr->begin()); if ( (quote == '\'' || quote == '\"') && (*(pStr->rbegin()) == quote) ) *pStr = pStr->substr(1, pStr->length()-2); // #nocov } // is the passed string quoted? bool isQuoted(const std::string& str) { if (str.length() < 2) return false; // #nocov char quote = *(str.begin()); return (quote == '\'' || quote == '\"') && (*(str.rbegin()) == quote); } // does a string end with another string? bool endsWith(const std::string& str, const std::string& suffix) { return str.size() >= suffix.size() && str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0; } // show a warning message void showWarning(const std::string& msg) { // #nocov start Rcpp::Function warning = Rcpp::Environment::base_env()["warning"]; warning(msg, Rcpp::Named("call.") = false); } // #nocov end bool isRoxygenCpp(const std::string& str) { size_t len = str.length(); if (len < 3) return false; size_t idx = str.find_first_not_of(kWhitespaceChars); if (idx == std::string::npos) return false; // make sure there are characters to check if (len - 2 < idx) return false; if (str[idx] == '/' && str[idx + 1] == '/' && str[idx + 2] == '\'') { return true; } return false; } } // namespace attributes } // namespace Rcpp /******************************************************************* * Attributes.cpp *******************************************************************/ using namespace Rcpp::attributes; // Implementation helpers for sourceCppContext namespace { // Class that manages generation of source code for the sourceCpp dynlib class SourceCppDynlib { public: SourceCppDynlib() {} SourceCppDynlib(const std::string& cacheDir, const std::string& cppSourcePath, Rcpp::List platform) : cppSourcePath_(cppSourcePath) { // get cpp source file info FileInfo cppSourceFilenameInfo(cppSourcePath_); if (!cppSourceFilenameInfo.exists()) throw Rcpp::file_not_found(cppSourcePath_); // #nocov // record the base name of the source file Rcpp::Function basename = Rcpp::Environment::base_env()["basename"]; cppSourceFilename_ = Rcpp::as(basename(cppSourcePath_)); // get platform info fileSep_ = Rcpp::as(platform["file.sep"]); dynlibExt_ = Rcpp::as(platform["dynlib.ext"]); // generate temp directory Rcpp::Function tempfile = Rcpp::Environment::base_env()["tempfile"]; buildDirectory_ = Rcpp::as(tempfile("sourcecpp_", cacheDir)); std::replace(buildDirectory_.begin(), buildDirectory_.end(), '\\', '/'); Rcpp::Function dircreate = Rcpp::Environment::base_env()["dir.create"]; dircreate(buildDirectory_); // generate a random context id contextId_ = "sourceCpp_" + uniqueToken(cacheDir); // regenerate the source code regenerateSource(cacheDir); } // create from list explicit SourceCppDynlib(const Rcpp::List& dynlib) { using namespace Rcpp; cppSourcePath_ = as(dynlib["cppSourcePath"]); generatedCpp_ = as(dynlib["generatedCpp"]); cppSourceFilename_ = as(dynlib["cppSourceFilename"]); contextId_ = as(dynlib["contextId"]); buildDirectory_ = as(dynlib["buildDirectory"]); fileSep_ = as(dynlib["fileSep"]); dynlibFilename_ = as(dynlib["dynlibFilename"]); previousDynlibFilename_ = as(dynlib["previousDynlibFilename"]); dynlibExt_ = as(dynlib["dynlibExt"]); exportedFunctions_ = as >(dynlib["exportedFunctions"]); modules_ = as >(dynlib["modules"]); depends_ = as >(dynlib["depends"]); plugins_ = as >(dynlib["plugins"]); embeddedR_ = as >(dynlib["embeddedR"]); List sourceDependencies = as(dynlib["sourceDependencies"]); for (R_xlen_t i = 0; i(sourceDependencies.at(i)); // #nocov sourceDependencies_.push_back(FileInfo(fileInfo)); // #nocov } } // convert to list Rcpp::List toList() const { using namespace Rcpp; List dynlib; dynlib["cppSourcePath"] = cppSourcePath_; dynlib["generatedCpp"] = generatedCpp_; dynlib["cppSourceFilename"] = cppSourceFilename_; dynlib["contextId"] = contextId_; dynlib["buildDirectory"] = buildDirectory_; dynlib["fileSep"] = fileSep_; dynlib["dynlibFilename"] = dynlibFilename_; dynlib["previousDynlibFilename"] = previousDynlibFilename_; dynlib["dynlibExt"] = dynlibExt_; dynlib["exportedFunctions"] = exportedFunctions_; dynlib["modules"] = modules_; dynlib["depends"] = depends_; dynlib["plugins"] = plugins_; dynlib["embeddedR"] = embeddedR_; List sourceDependencies; for (std::size_t i = 0; i FileInfo(generatedCppSourcePath()).lastModified()) return true; // #nocov // no dynlib means we're dirty if (!FileInfo(dynlibPath()).exists()) return true; // #nocov // variation in source dependencies means we're dirty std::vector sourceDependencies = parseSourceDependencies( cppSourcePath_); if (sourceDependencies != sourceDependencies_) return true; // #nocov // not dirty return false; } void regenerateSource(const std::string& cacheDir) { // create new dynlib filename previousDynlibFilename_ = dynlibFilename_; dynlibFilename_ = "sourceCpp_" + uniqueToken(cacheDir) + dynlibExt_; // copy the source file to the build dir Rcpp::Function filecopy = Rcpp::Environment::base_env()["file.copy"]; filecopy(cppSourcePath_, generatedCppSourcePath(), true, Rcpp::_["copy.mode"] = false); // parse attributes SourceFileAttributesParser sourceAttributes(cppSourcePath_, "", true); // generate cpp for attributes and append them std::ostringstream ostr; // always include Rcpp.h in case the user didn't ostr << std::endl << std::endl; ostr << "#include " << std::endl; // initialize references to global Rostreams initializeGlobals(ostr); generateCpp(ostr, sourceAttributes, true, false, contextId_); generatedCpp_ = ostr.str(); std::ofstream cppOfs(generatedCppSourcePath().c_str(), std::ofstream::out | std::ofstream::app); if (cppOfs.fail()) throw Rcpp::file_io_error(generatedCppSourcePath()); // #nocov cppOfs << generatedCpp_; cppOfs.close(); // generate R for attributes and write it into the build directory std::ofstream rOfs(generatedRSourcePath().c_str(), std::ofstream::out | std::ofstream::trunc); if (rOfs.fail()) throw Rcpp::file_io_error(generatedRSourcePath()); // #nocov // DLLInfo - hide using . and ensure uniqueness using contextId std::string dllInfo = "`." + contextId_ + "_DLLInfo`"; rOfs << dllInfo << " <- dyn.load('" << dynlibPath() << "')" << std::endl << std::endl; // Generate R functions generateR(rOfs, sourceAttributes, dllInfo); // remove the DLLInfo rOfs << std::endl << "rm(" << dllInfo << ")" << std::endl; rOfs.close(); // discover exported functions and dependencies exportedFunctions_.clear(); depends_.clear(); plugins_.clear(); for (SourceFileAttributesParser::const_iterator it = sourceAttributes.begin(); it != sourceAttributes.end(); ++it) { if (it->name() == kExportAttribute && !it->function().empty()) exportedFunctions_.push_back(it->exportedName()); else if (it->name() == kDependsAttribute) { for (size_t i = 0; iparams().size(); ++i) // #nocov depends_.push_back(it->params()[i].name()); // #nocov } else if (it->name() == kPluginsAttribute) { for (size_t i = 0; iparams().size(); ++i) plugins_.push_back(it->params()[i].name()); } } // capture modules modules_ = sourceAttributes.modules(); // capture embededded R embeddedR_ = sourceAttributes.embeddedR(); // capture source dependencies sourceDependencies_ = sourceAttributes.sourceDependencies(); } const std::string& contextId() const { return contextId_; } const std::string& cppSourcePath() const { return cppSourcePath_; } const std::vector cppDependencySourcePaths() { std::vector dependencies; for (size_t i = 0; i& exportedFunctions() const { return exportedFunctions_; } const std::vector& modules() const { return modules_; } const std::vector& depends() const { return depends_; }; const std::vector& plugins() const { return plugins_; }; const std::vector& embeddedR() const { return embeddedR_; } private: std::string generatedCppSourcePath() const { return buildDirectory_ + fileSep_ + cppSourceFilename(); } std::string generatedRSourcePath() const { return buildDirectory_ + fileSep_ + rSourceFilename(); } void generateR(std::ostream& ostr, const SourceFileAttributes& attributes, const std::string& dllInfo) const { // process each attribute for(std::vector::const_iterator it = attributes.begin(); it != attributes.end(); ++it) { // alias the attribute and function (bail if not export) const Attribute& attribute = *it; if (!attribute.isExportedFunction()) continue; const Function& function = attribute.function(); // build the parameter list std::string args = generateRArgList(function); // check if has a custom signature if(attribute.hasParameter(kExportSignature)) { args = attribute.customRSignature(); if(!checkRSignature(function, args)) { std::string rsig_err_msg = "Missing args in " + args; throw Rcpp::exception(rsig_err_msg.c_str()); } } // export the function ostr << attribute.exportedName() << " <- Rcpp:::sourceCppFunction(" << "function(" << args << ") {}, " << (function.type().isVoid() ? "TRUE" : "FALSE") << ", " << dllInfo << ", " << "'" << contextId_ + "_" + function.name() << "')" << std::endl; } // modules std::vector modules = attributes.modules(); if (modules.size() > 0) { // modules require definition of C++Object to be loaded ostr << "library(Rcpp)" << std::endl; // load each module for (std::vector::const_iterator it = modules.begin(); it != modules.end(); ++it) { ostr << " populate( Rcpp::Module(\"" << *it << "\"," << dllInfo << "), environment() ) " << std::endl; } } } std::string uniqueToken(const std::string& cacheDir) { Rcpp::Environment rcppEnv = Rcpp::Environment::namespace_env("Rcpp"); Rcpp::Function uniqueTokenFunc = rcppEnv[".sourceCppDynlibUniqueToken"]; return Rcpp::as(uniqueTokenFunc(cacheDir)); } private: std::string cppSourcePath_; std::string generatedCpp_; std::string cppSourceFilename_; std::string contextId_; std::string buildDirectory_; std::string fileSep_; std::string dynlibFilename_; std::string previousDynlibFilename_; std::string dynlibExt_; std::vector exportedFunctions_; std::vector modules_; std::vector depends_; std::vector plugins_; std::vector embeddedR_; std::vector sourceDependencies_; }; // Dynlib cache that allows lookup by either file path or code contents void dynlibCacheInsert(const std::string& cacheDir, const std::string& file, const std::string& code, const SourceCppDynlib& dynlib) { Rcpp::Environment rcppEnv = Rcpp::Environment::namespace_env("Rcpp"); Rcpp::Function dynlibInsertFunc = rcppEnv[".sourceCppDynlibInsert"]; dynlibInsertFunc(cacheDir, file, code, dynlib.toList()); } void dynlibCacheInsertFile(const std::string& cacheDir, const std::string& file, const SourceCppDynlib& dynlib) { dynlibCacheInsert(cacheDir, file, "", dynlib); } void dynlibCacheInsertCode(const std::string& cacheDir, const std::string& code, const SourceCppDynlib& dynlib) { dynlibCacheInsert(cacheDir, "", code, dynlib); } SourceCppDynlib dynlibCacheLookup(const std::string& cacheDir, const std::string& file, const std::string& code) { Rcpp::Environment rcppEnv = Rcpp::Environment::namespace_env("Rcpp"); Rcpp::Function dynlibLookupFunc = rcppEnv[".sourceCppDynlibLookup"]; Rcpp::List dynlibList = dynlibLookupFunc(cacheDir, file, code); if (dynlibList.length() > 0) return SourceCppDynlib(dynlibList); else return SourceCppDynlib(); } SourceCppDynlib dynlibCacheLookupByFile(const std::string& cacheDir, const std::string& file) { return dynlibCacheLookup(cacheDir, file, ""); } SourceCppDynlib dynlibCacheLookupByCode(const std::string& cacheDir, const std::string& code) { return dynlibCacheLookup(cacheDir, "", code); } } // anonymous namespace // 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 sCode, SEXP sRebuild, SEXP sCacheDir, SEXP sPlatform) { BEGIN_RCPP // parameters std::string file = Rcpp::as(sFile); std::string code = sCode != R_NilValue ? Rcpp::as(sCode) : ""; bool rebuild = Rcpp::as(sRebuild); std::string cacheDir = Rcpp::as(sCacheDir); Rcpp::List platform = Rcpp::as(sPlatform); // get dynlib (using cache if possible) SourceCppDynlib dynlib = !code.empty() ? dynlibCacheLookupByCode(cacheDir, code) : dynlibCacheLookupByFile(cacheDir, file); // check dynlib build state bool buildRequired = false; // if there is no dynlib in the cache then create a new one if (dynlib.isEmpty()) { buildRequired = true; dynlib = SourceCppDynlib(cacheDir, file, platform); } // if the cached dynlib is dirty then regenerate the source else if (rebuild || dynlib.isSourceDirty()) { buildRequired = true; // #nocov dynlib.regenerateSource(cacheDir); // #nocov } // if the dynlib hasn't yet been built then note that else if (!dynlib.isBuilt()) { buildRequired = true; // #nocov } // save the dynlib to the cache if (!code.empty()) dynlibCacheInsertCode(cacheDir, code, dynlib); else dynlibCacheInsertFile(cacheDir, file, dynlib); // return context as a list using namespace Rcpp; return List::create( _["contextId"] = dynlib.contextId(), _["cppSourcePath"] = dynlib.cppSourcePath(), _["cppDependencySourcePaths"] = dynlib.cppDependencySourcePaths(), _["buildRequired"] = buildRequired, _["buildDirectory"] = dynlib.buildDirectory(), _["generatedCpp"] = dynlib.generatedCpp(), _["exportedFunctions"] = dynlib.exportedFunctions(), _["modules"] = dynlib.modules(), _["cppSourceFilename"] = dynlib.cppSourceFilename(), _["rSourceFilename"] = dynlib.rSourceFilename(), _["dynlibFilename"] = dynlib.dynlibFilename(), _["dynlibPath"] = dynlib.dynlibPath(), _["previousDynlibPath"] = dynlib.previousDynlibPath(), _["depends"] = dynlib.depends(), _["plugins"] = dynlib.plugins(), _["embeddedR"] = dynlib.embeddedR()); END_RCPP } // Compile the attributes within the specified package directory into // RcppExports.cpp and RcppExports.R RcppExport SEXP compileAttributes(SEXP sPackageDir, SEXP sPackageName, SEXP sDepends, SEXP sRegistration, SEXP sCppFiles, SEXP sCppFileBasenames, SEXP sIncludes, SEXP sVerbose, SEXP sPlatform) { BEGIN_RCPP // arguments std::string packageDir = Rcpp::as(sPackageDir); std::string packageName = Rcpp::as(sPackageName); Rcpp::CharacterVector vDepends = Rcpp::as(sDepends); std::set depends; for (Rcpp::CharacterVector::iterator it = vDepends.begin(); it != vDepends.end(); ++it) { depends.insert(std::string(*it)); } bool registration = Rcpp::as(sRegistration); std::vector cppFiles = Rcpp::as >(sCppFiles); std::vector cppFileBasenames = Rcpp::as >(sCppFileBasenames); std::vector includes = Rcpp::as >(sIncludes); bool verbose = Rcpp::as(sVerbose); Rcpp::List platform = Rcpp::as(sPlatform); std::string fileSep = Rcpp::as(platform["file.sep"]); // initialize generators ExportsGenerators generators; generators.add(new CppExportsGenerator(packageDir, packageName, fileSep)); generators.add(new RExportsGenerator(packageDir, packageName, registration, fileSep)); // catch file exists exception if the include file already exists // and we are unable to overwrite it try { generators.add(new CppExportsIncludeGenerator(packageDir, packageName, fileSep)); } catch(const Rcpp::file_exists& e) { std::string msg = "The header file '" + e.filePath() + "' already exists so " "cannot be overwritten by Rcpp::interfaces"; throw Rcpp::exception(msg.c_str(), __FILE__, __LINE__); } // catch file exists exception for package include (because if it // already exists we simply leave it alone) try { generators.add(new CppPackageIncludeGenerator(packageDir, packageName, fileSep)); } catch(const Rcpp::file_exists& e) {} // write begin generators.writeBegin(); // Parse attributes from each file and generate code as required. bool hasPackageInit = false; bool haveAttributes = false; std::set dependsAttribs; for (std::size_t i=0; iname() == kDependsAttribute) { for (size_t i = 0; iparams().size(); ++i) // #nocov dependsAttribs.insert(it->params()[i].name()); // #nocov } } } // write end generators.writeEnd(hasPackageInit); // commit or remove std::vector updated; if (haveAttributes) updated = generators.commit(includes); else updated = generators.remove(); // #nocov // print warning if there are depends attributes that don't have // corresponding entries in the DESCRIPTION file std::vector diff; std::set_difference(dependsAttribs.begin(), dependsAttribs.end(), depends.begin(), depends.end(), std::back_inserter(diff)); if (!diff.empty()) { std::string msg = // #nocov start "The following packages are referenced using Rcpp::depends " "attributes however are not listed in the Depends, Imports or " "LinkingTo fields of the package DESCRIPTION file: "; for (size_t i=0; i >(updated); END_RCPP }