1 //
2 // attributes.cpp: Rcpp R/C++ interface class library -- Rcpp attributes
3 //
4 // Copyright (C) 2012 - 2020 JJ Allaire, Dirk Eddelbuettel and Romain Francois
5 // Copyright (C) 2021 JJ Allaire, Dirk Eddelbuettel, Romain Francois and Iñaki Ucar
6 //
7 // This file is part of Rcpp.
8 //
9 // Rcpp is free software: you can redistribute it and/or modify it
10 // under the terms of the GNU General Public License as published by
11 // the Free Software Foundation, either version 2 of the License, or
12 // (at your option) any later version.
13 //
14 // Rcpp is distributed in the hope that it will be useful, but
15 // WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 // GNU General Public License for more details.
18 //
19 // You should have received a copy of the GNU General Public License
20 // along with Rcpp. If not, see <http://www.gnu.org/licenses/>.
21
22 #define COMPILING_RCPP
23
24 #include <sys/types.h>
25 #include <sys/stat.h>
26 #include <errno.h>
27
28 #include <cstring>
29
30 #include <string>
31 #include <vector>
32 #include <map>
33 #include <set>
34 #include <algorithm>
35 #include <fstream>
36 #include <sstream>
37
38 #define RCPP_NO_SUGAR
39 #include <Rcpp.h>
40
41 /*******************************************************************
42 * AttributesUtil.h
43 *******************************************************************/
44
45 namespace Rcpp {
46 namespace attributes {
47
48 // Utility class for getting file existence and last modified time
49 class FileInfo {
50 public:
51
52 // create from path
53 explicit FileInfo(const std::string& path);
54
55 // create from R list
FileInfo(const List & fileInfo)56 explicit FileInfo(const List& fileInfo) { // #nocov start
57 path_ = as<std::string>(fileInfo["path"]);
58 exists_ = as<bool>(fileInfo["exists"]);
59 lastModified_ = as<double>(fileInfo["lastModified"]);
60 } // #nocov end
61
62 // convert to R list
toList() const63 List toList() const {
64 List fileInfo;
65 fileInfo["path"] = path_;
66 fileInfo["exists"] = exists_;
67 fileInfo["lastModified"] = lastModified_;
68 return fileInfo;
69 }
70
path() const71 std::string path() const { return path_; }
exists() const72 bool exists() const { return exists_; }
lastModified() const73 double lastModified() const { return lastModified_; }
74
extension() const75 std::string extension() const {
76 std::string::size_type pos = path_.find_last_of('.');
77 if (pos != std::string::npos)
78 return path_.substr(pos);
79 else
80 return ""; // #nocov
81 }
82
operator <(const FileInfo & other) const83 bool operator<(const FileInfo& other) const {
84 return path_ < other.path_;
85 };
86
operator ==(const FileInfo & other) const87 bool operator==(const FileInfo& other) const {
88 return path_ == other.path_ &&
89 exists_ == other.exists_ &&
90 lastModified_ == other.lastModified_;
91 };
92
operator !=(const FileInfo & other) const93 bool operator!=(const FileInfo& other) const {
94 return ! (*this == other);
95 };
96
operator <<(std::ostream & os) const97 std::ostream& operator<<(std::ostream& os) const {
98 os << path_;
99 return os;
100 }
101
102 private:
103 std::string path_;
104 bool exists_;
105 double lastModified_;
106 };
107
108 // Remove a file
109 bool removeFile(const std::string& path);
110
111 // Recursively create a directory
112 void createDirectory(const std::string& path);
113
114 // Known whitespace chars
115 extern const char * const kWhitespaceChars;
116
117 // Query whether a character is whitespace
118 bool isWhitespace(char ch);
119
120 // Trim a string
121 void trimWhitespace(std::string* pStr);
122
123 // Strip trailing line comments
124 void stripTrailingLineComments(std::string* pStr);
125
126 // Strip balanced quotes from around a string (assumes already trimmed)
127 void stripQuotes(std::string* pStr);
128
129 // is the passed string quoted?
130 bool isQuoted(const std::string& str);
131
132 // does a string end with another string?
133 bool endsWith(const std::string& str, const std::string& suffix);
134
135 // show a warning message
136 void showWarning(const std::string& msg);
137
138 // is the line a C++ roxygen comment? (started with //')
139 bool isRoxygenCpp(const std::string& str);
140
141 } // namespace attributes
142 } // namespace Rcpp
143
144
145 /*******************************************************************
146 * AttributesTypes.h
147 *******************************************************************/
148
149 namespace Rcpp {
150 namespace attributes {
151
152 // Known attribute names & parameters
153 const char * const kExportAttribute = "export";
154 const char * const kExportName = "name";
155 const char * const kExportRng = "rng";
156 const char * const kExportInvisible = "invisible";
157 const char * const kInitAttribute = "init";
158 const char * const kDependsAttribute = "depends";
159 const char * const kPluginsAttribute = "plugins";
160 const char * const kInterfacesAttribute = "interfaces";
161 const char * const kInterfaceR = "r";
162 const char * const kInterfaceCpp = "cpp";
163 const char * const kParamValueFalse = "false";
164 const char * const kParamValueTrue = "true";
165 const char * const kParamValueFALSE = "FALSE";
166 const char * const kParamValueTRUE = "TRUE";
167
168 // Type info
169 class Type {
170 public:
Type()171 Type(): isConst_(false), isReference_(false) {}
Type(const std::string & name,bool isConst,bool isReference)172 Type(const std::string& name, bool isConst, bool isReference)
173 : name_(name), isConst_(isConst), isReference_(isReference)
174 {
175 }
empty() const176 bool empty() const { return name().empty(); }
177
operator ==(const Type & other) const178 bool operator==(const Type& other) const { // #nocov start
179 return name_ == other.name_ &&
180 isConst_ == other.isConst_ &&
181 isReference_ == other.isReference_;
182 }; // #nocov end
183
operator !=(const Type & other) const184 bool operator!=(const Type& other) const {
185 return !(*this == other);
186 };
187
name() const188 const std::string& name() const { return name_; }
full_name() const189 std::string full_name() const {
190 std::string res ;
191 if( isConst() ) res += "const " ;
192 res += name() ;
193 if( isReference() ) res += "&" ;
194 return res ;
195 }
196
isVoid() const197 bool isVoid() const { return name() == "void"; }
isConst() const198 bool isConst() const { return isConst_; }
isReference() const199 bool isReference() const { return isReference_; }
200
201 private:
202 std::string name_;
203 bool isConst_;
204 bool isReference_;
205 };
206
207 // Argument info
208 class Argument {
209 public:
Argument()210 Argument() {}
Argument(const std::string & name,const Type & type,const std::string & defaultValue)211 Argument(const std::string& name,
212 const Type& type,
213 const std::string& defaultValue)
214 : name_(name), type_(type), defaultValue_(defaultValue)
215 {
216 }
217
empty() const218 bool empty() const { return type().empty(); }
219
operator ==(const Argument & other) const220 bool operator==(const Argument& other) const { // #nocov start
221 return name_ == other.name_ &&
222 type_ == other.type_ &&
223 defaultValue_ == other.defaultValue_;
224 }; // #nocov end
225
operator !=(const Argument & other) const226 bool operator!=(const Argument& other) const {
227 return !(*this == other);
228 };
229
230
name() const231 const std::string& name() const { return name_; }
type() const232 const Type& type() const { return type_; }
defaultValue() const233 const std::string& defaultValue() const { return defaultValue_; }
234
235 private:
236 std::string name_;
237 Type type_;
238 std::string defaultValue_;
239 };
240
241 // Function info
242 class Function {
243 public:
Function()244 Function() {}
Function(const Type & type,const std::string & name,const std::vector<Argument> & arguments)245 Function(const Type& type,
246 const std::string& name,
247 const std::vector<Argument>& arguments)
248 : type_(type), name_(name), arguments_(arguments)
249 {
250 }
251
renamedTo(const std::string & name) const252 Function renamedTo(const std::string& name) const { // #nocov start
253 return Function(type(), name, arguments());
254 }
255
signature() const256 std::string signature() const { return signature(name()); }
257 std::string signature(const std::string& name) const;
258
isHidden() const259 bool isHidden() const {
260 return name().find_first_of('.') == 0;
261 } // #nocov end
262
empty() const263 bool empty() const { return name().empty(); }
264
operator ==(const Function & other) const265 bool operator==(const Function& other) const { // #nocov start
266 return type_ == other.type_ &&
267 name_ == other.name_ &&
268 arguments_ == other.arguments_;
269 }; // #nocov end
270
operator !=(const Function & other) const271 bool operator!=(const Function& other) const {
272 return !(*this == other);
273 };
274
type() const275 const Type& type() const { return type_; }
name() const276 const std::string& name() const { return name_; }
arguments() const277 const std::vector<Argument>& arguments() const { return arguments_; }
278
279 private:
280 Type type_;
281 std::string name_;
282 std::vector<Argument> arguments_;
283 };
284
285 // Attribute parameter (with optional value)
286 class Param {
287 public:
Param()288 Param() {}
289 explicit Param(const std::string& paramText);
empty() const290 bool empty() const { return name().empty(); }
291
operator ==(const Param & other) const292 bool operator==(const Param& other) const { // #nocov start
293 return name_ == other.name_ &&
294 value_ == other.value_;
295 }; // #nocov end
296
operator !=(const Param & other) const297 bool operator!=(const Param& other) const {
298 return !(*this == other);
299 };
300
301
name() const302 const std::string& name() const { return name_; }
value() const303 const std::string& value() const { return value_; } // #nocov
304
305 private:
306 std::string name_;
307 std::string value_;
308 };
309
310 // Attribute (w/ optional params and signature of function it qualifies)
311 class Attribute {
312 public:
Attribute()313 Attribute() {}
Attribute(const std::string & name,const std::vector<Param> & params,const Function & function,const std::vector<std::string> & roxygen)314 Attribute(const std::string& name,
315 const std::vector<Param>& params,
316 const Function& function,
317 const std::vector<std::string>& roxygen)
318 : name_(name), params_(params), function_(function), roxygen_(roxygen)
319 {
320 }
321
empty() const322 bool empty() const { return name().empty(); } // #nocov start
323
operator ==(const Attribute & other) const324 bool operator==(const Attribute& other) const {
325 return name_ == other.name_ &&
326 params_ == other.params_ &&
327 function_ == other.function_ &&
328 roxygen_ == other.roxygen_;
329 }; // #nocov end
330
operator !=(const Attribute & other) const331 bool operator!=(const Attribute& other) const {
332 return !(*this == other);
333 };
334
335
name() const336 const std::string& name() const { return name_; }
337
params() const338 const std::vector<Param>& params() const { return params_; }
339
340 Param paramNamed(const std::string& name) const;
341
hasParameter(const std::string & name) const342 bool hasParameter(const std::string& name) const {
343 return !paramNamed(name).empty();
344 }
345
function() const346 const Function& function() const { return function_; }
347
isExportedFunction() const348 bool isExportedFunction() const {
349 return (name() == kExportAttribute) && !function().empty();
350 }
351
exportedName() const352 std::string exportedName() const {
353
354 // check for explicit name parameter
355 if (hasParameter(kExportName))
356 {
357 return paramNamed(kExportName).value(); // #nocov
358 }
359 // otherwise un-named parameter in the first slot
360 else if (!params().empty() && params()[0].value().empty())
361 {
362 return params()[0].name(); // #nocov
363 }
364 // otherwise the actual function name
365 {
366 return function().name();
367 }
368 }
369
exportedCppName() const370 std::string exportedCppName() const { // #nocov start
371 std::string name = exportedName();
372 std::replace(name.begin(), name.end(), '.', '_');
373 return name;
374 } // #nocov end
375
rng() const376 bool rng() const {
377 Param rngParam = paramNamed(kExportRng);
378 if (!rngParam.empty())
379 return rngParam.value() == kParamValueTrue || // #nocov
380 rngParam.value() == kParamValueTRUE; // #nocov
381 else
382 return true;
383 }
384
invisible() const385 bool invisible() const {
386 Param invisibleParam = paramNamed(kExportInvisible);
387 if (!invisibleParam.empty())
388 return invisibleParam.value() == kParamValueTrue || // #nocov
389 invisibleParam.value() == kParamValueTRUE; // #nocov
390 else
391 return false;
392 }
393
roxygen() const394 const std::vector<std::string>& roxygen() const { return roxygen_; }
395
396 private:
397 std::string name_;
398 std::vector<Param> params_;
399 Function function_;
400 std::vector<std::string> roxygen_;
401 };
402
403 // Operator << for parsed types
404 std::ostream& operator<<(std::ostream& os, const Type& type);
405 std::ostream& operator<<(std::ostream& os, const Argument& argument);
406 std::ostream& operator<<(std::ostream& os, const Function& function);
407 std::ostream& operator<<(std::ostream& os, const Param& param);
408 std::ostream& operator<<(std::ostream& os, const Attribute& attribute);
409
410 // interface to source file attributes
411 class SourceFileAttributes
412 {
413 public:
~SourceFileAttributes()414 virtual ~SourceFileAttributes() {};
415 virtual const std::string& sourceFile() const = 0;
416 virtual bool hasInterface(const std::string& name) const = 0;
417
418 typedef std::vector<Attribute>::const_iterator const_iterator;
419 virtual const_iterator begin() const = 0;
420 virtual const_iterator end() const = 0;
421
422 virtual const std::vector<std::string>& modules() const = 0;
423
424 virtual const std::vector<std::vector<std::string> >& roxygenChunks() const = 0;
425
426 virtual bool hasGeneratorOutput() const = 0;
427
428 virtual bool hasPackageInit() const = 0;
429 };
430
431
432 } // namespace attributes
433 } // namespace Rcpp
434
435
436
437 /*******************************************************************
438 * AttributesParser.h
439 *******************************************************************/
440
441 namespace Rcpp {
442 namespace attributes {
443
444 // Helper class for determining whether we are in a comment
445 class CommentState {
446 public:
CommentState()447 CommentState() : inComment_(false) {}
448 private:
449 // prohibit copying
450 CommentState(const CommentState&);
451 CommentState& operator=(const CommentState&);
452 public:
inComment() const453 bool inComment() const { return inComment_; }
454 void submitLine(const std::string& line);
reset()455 void reset() { inComment_ = false; }
456 private:
457 bool inComment_;
458 };
459
460 // Class used to parse and return attribute information from a source file
461 class SourceFileAttributesParser : public SourceFileAttributes {
462 public:
463 explicit SourceFileAttributesParser(const std::string& sourceFile,
464 const std::string& packageFile,
465 bool parseDependencies);
466
467 private:
468 // prohibit copying
469 SourceFileAttributesParser(const SourceFileAttributesParser&);
470 SourceFileAttributesParser& operator=(const SourceFileAttributesParser&);
471
472 public:
473 // implemetnation of SourceFileAttributes interface
sourceFile() const474 virtual const std::string& sourceFile() const { // #nocov
475 return sourceFile_; // #nocov
476 }
begin() const477 virtual const_iterator begin() const { return attributes_.begin(); }
end() const478 virtual const_iterator end() const { return attributes_.end(); }
479
modules() const480 virtual const std::vector<std::string>& modules() const
481 {
482 return modules_;
483 }
484
roxygenChunks() const485 virtual const std::vector<std::vector<std::string> >& roxygenChunks() const {
486 return roxygenChunks_;
487 }
488
hasGeneratorOutput() const489 virtual bool hasGeneratorOutput() const
490 {
491 return !attributes_.empty() ||
492 !modules_.empty() ||
493 !roxygenChunks_.empty();
494 }
495
hasInterface(const std::string & name) const496 virtual bool hasInterface(const std::string& name) const {
497
498 for (const_iterator it=begin(); it != end(); ++it) {
499 if (it->name() == kInterfacesAttribute) {
500 return it->hasParameter(name); // #nocov
501 }
502 }
503
504 // if there's no interfaces attrbute we default to R
505 if (name == kInterfaceR)
506 return true;
507 else
508 return false;
509 }
510
511 // Was a package init function found?
hasPackageInit() const512 bool hasPackageInit() const {
513 return hasPackageInit_;
514 }
515
516 // Get lines of embedded R code
embeddedR() const517 const std::vector<std::string>& embeddedR() const {
518 return embeddedR_;
519 }
520
521 // Get source dependencies
sourceDependencies() const522 const std::vector<FileInfo>& sourceDependencies() const {
523 return sourceDependencies_;
524 };
525
526 private:
527
528 // Parsing helpers
529 Attribute parseAttribute(const std::vector<std::string>& match,
530 int lineNumber);
531 std::vector<Param> parseParameters(const std::string& input);
532 Function parseFunction(size_t lineNumber);
533 std::string parseSignature(size_t lineNumber);
534 std::vector<std::string> parseArguments(const std::string& argText);
535 Type parseType(const std::string& text);
536
537 // Validation helpers
538 bool isKnownAttribute(const std::string& name) const;
539 void attributeWarning(const std::string& message,
540 const std::string& attribute,
541 size_t lineNumber);
542 void attributeWarning(const std::string& message, size_t lineNumber);
543 void rcppExportWarning(const std::string& message, size_t lineNumber);
544 void rcppExportNoFunctionFoundWarning(size_t lineNumber);
545 void rcppExportInvalidParameterWarning(const std::string& param,
546 size_t lineNumber);
547 void rcppInterfacesWarning(const std::string& message,
548 size_t lineNumber);
549
550 private:
551 std::string sourceFile_;
552 CharacterVector lines_;
553 std::vector<Attribute> attributes_;
554 std::vector<std::string> modules_;
555 bool hasPackageInit_;
556 std::vector<std::string> embeddedR_;
557 std::vector<FileInfo> sourceDependencies_;
558 std::vector<std::vector<std::string> > roxygenChunks_;
559 std::vector<std::string> roxygenBuffer_;
560 };
561
562 } // namespace attributes
563 } // namespace Rcpp
564
565
566 /*******************************************************************
567 * AttributesGen.h
568 *******************************************************************/
569
570 namespace Rcpp {
571 namespace attributes {
572
573 // Abstract class which manages writing of code for compileAttributes
574 class ExportsGenerator {
575 protected:
576 ExportsGenerator(const std::string& targetFile,
577 const std::string& package,
578 const std::string& commentPrefix);
579
580 private:
581 // prohibit copying
582 ExportsGenerator(const ExportsGenerator&);
583 ExportsGenerator& operator=(const ExportsGenerator&);
584
585 public:
~ExportsGenerator()586 virtual ~ExportsGenerator() {}
587
588 // Name of target file and package
targetFile() const589 const std::string& targetFile() const { return targetFile_; }
package() const590 const std::string& package() const { return package_; }
packageCpp() const591 const std::string& packageCpp() const { return packageCpp_; }
packageCppPrefix() const592 const std::string packageCppPrefix() const { return "_" + packageCpp(); }
593
594 // Abstract interface for code generation
595 virtual void writeBegin() = 0;
596 void writeFunctions(const SourceFileAttributes& attributes,
597 bool verbose); // see doWriteFunctions below
598 virtual void writeEnd(bool hasPackageInit) = 0;
599
600 virtual bool commit(const std::vector<std::string>& includes) = 0;
601
602 // Remove the generated file entirely
603 bool remove();
604
605 // Allow generator to appear as a std::ostream&
operator std::ostream&()606 operator std::ostream&() {
607 return codeStream_;
608 }
609
610 protected:
611
612 // Allow access to the output stream
ostr()613 std::ostream& ostr() {
614 return codeStream_;
615 }
616
hasCppInterface() const617 bool hasCppInterface() const {
618 return hasCppInterface_;
619 }
620
621 // Shared knowledge about function namees
exportValidationFunction()622 std::string exportValidationFunction() {
623 return "RcppExport_validate";
624 }
exportValidationFunctionRegisteredName()625 std::string exportValidationFunctionRegisteredName() {
626 return packageCppPrefix() + "_" + exportValidationFunction();
627 }
registerCCallableExportedName()628 std::string registerCCallableExportedName() { // #nocov
629 return packageCppPrefix() + "_RcppExport_registerCCallable"; // #nocov
630 }
631
632 // Commit the stream -- is a no-op if the existing code is identical
633 // to the generated code. Returns true if data was written and false
634 // if it wasn't (throws exception on io error)
635 bool commit(const std::string& preamble = std::string());
636
637 // Convert a dot in package name to underscore for use in header file name
638 std::string dotNameHelper(const std::string & name) const;
639
640 private:
641
642 // Private virtual for doWriteFunctions so the base class
643 // can always intercept writeFunctions
644 virtual void doWriteFunctions(const SourceFileAttributes& attributes,
645 bool verbose) = 0;
646
647 // Check whether it's safe to overwrite this file (i.e. whether we
648 // generated the file in the first place)
isSafeToOverwrite() const649 bool isSafeToOverwrite() const {
650 return existingCode_.empty() ||
651 (existingCode_.find(generatorToken()) != std::string::npos);
652 }
653
654 // UUID that we write into a comment within the file (so that we can
655 // strongly identify that a file was generated by us before overwriting it)
generatorToken() const656 std::string generatorToken() const {
657 return "10BE3573-1514-4C36-9D1C-5A225CD40393";
658 }
659
660 private:
661 std::string targetFile_;
662 std::string package_;
663 std::string packageCpp_;
664 std::string commentPrefix_;
665 std::string existingCode_;
666 std::ostringstream codeStream_;
667 bool hasCppInterface_;
668 };
669
670 // Class which manages generating RcppExports.cpp
671 class CppExportsGenerator : public ExportsGenerator {
672 public:
673 explicit CppExportsGenerator(const std::string& packageDir,
674 const std::string& package,
675 const std::string& fileSep);
676
writeBegin()677 virtual void writeBegin() {};
678 virtual void writeEnd(bool hasPackageInit);
679 virtual bool commit(const std::vector<std::string>& includes);
680
681 private:
682 virtual void doWriteFunctions(const SourceFileAttributes& attributes,
683 bool verbose);
684
685 std::string registerCCallable(size_t indent,
686 const std::string& exportedName,
687 const std::string& name) const;
688
689 private:
690 // for generating calls to init functions
691 std::vector<Attribute> initFunctions_;
692
693 // for generating C++ interfaces
694 std::vector<Attribute> cppExports_;
695
696 // for generating Rcpp::export native routine registration
697 std::vector<Attribute> nativeRoutines_;
698
699 // for generating module native routine registration
700 std::vector<std::string> modules_;
701 };
702
703 // Class which manages generating PackageName_RcppExports.h header file
704 class CppExportsIncludeGenerator : public ExportsGenerator {
705 public:
706 CppExportsIncludeGenerator(const std::string& packageDir,
707 const std::string& package,
708 const std::string& fileSep);
709
710 virtual void writeBegin();
711 virtual void writeEnd(bool hasPackageInit);
712 virtual bool commit(const std::vector<std::string>& includes);
713
714 private:
715 virtual void doWriteFunctions(const SourceFileAttributes& attributes,
716 bool verbose);
717 std::string getCCallable(const std::string& function) const;
718 std::string getHeaderGuard() const;
719
720 private:
721 std::string includeDir_;
722 };
723
724 // Class which manages generating PackageName.h header file
725 class CppPackageIncludeGenerator : public ExportsGenerator {
726 public:
727 CppPackageIncludeGenerator(const std::string& packageDir,
728 const std::string& package,
729 const std::string& fileSep);
730
writeBegin()731 virtual void writeBegin() {}
732 virtual void writeEnd(bool hasPackageInit);
733 virtual bool commit(const std::vector<std::string>& includes);
734
735 private:
doWriteFunctions(const SourceFileAttributes &,bool)736 virtual void doWriteFunctions(const SourceFileAttributes&, bool) {}
737 std::string getHeaderGuard() const;
738
739 private:
740 std::string includeDir_;
741 };
742
743
744 // Class which manages generator RcppExports.R
745 class RExportsGenerator : public ExportsGenerator {
746 public:
747 RExportsGenerator(const std::string& packageDir,
748 const std::string& package,
749 bool registration,
750 const std::string& fileSep);
751
writeBegin()752 virtual void writeBegin() {}
753 virtual void writeEnd(bool hasPackageInit);
754 virtual bool commit(const std::vector<std::string>& includes);
755
756 private:
757 virtual void doWriteFunctions(const SourceFileAttributes& attributes,
758 bool verbose);
759
760 bool registration_;
761
762 };
763
764 // Class to manage and dispatch to a list of generators
765 class ExportsGenerators {
766 public:
767 typedef std::vector<ExportsGenerator*>::iterator Itr;
768
ExportsGenerators()769 ExportsGenerators() {}
770 virtual ~ExportsGenerators();
771
772 void add(ExportsGenerator* pGenerator);
773
774 void writeBegin();
775 void writeFunctions(const SourceFileAttributes& attributes,
776 bool verbose);
777 void writeEnd(bool hasPackageInit);
778
779 // Commit and return a list of the files that were updated
780 std::vector<std::string> commit(
781 const std::vector<std::string>& includes);
782
783 // Remove and return a list of files that were removed
784 std::vector<std::string> remove();
785
786 private:
787 // prohibit copying
788 ExportsGenerators(const ExportsGenerators&);
789 ExportsGenerators& operator=(const ExportsGenerators&);
790
791 private:
792 std::vector<ExportsGenerator*> generators_;
793 };
794
795 // Standalone generation helpers (used by sourceCpp)
796
797 std::string generateRArgList(const Function& function);
798
799 void initializeGlobals(std::ostream& ostr);
800
801 void generateCpp(std::ostream& ostr,
802 const SourceFileAttributes& attributes,
803 bool includePrototype,
804 bool cppInterface,
805 const std::string& contextId);
806
807 } // namespace attributes
808 } // namespace Rcpp
809
810
811 /*******************************************************************
812 * AttributesParser.cpp
813 *******************************************************************/
814
815 namespace Rcpp {
816 namespace attributes {
817
818 namespace {
819
regexMatches(Rcpp::CharacterVector lines,const std::string & regex)820 Rcpp::List regexMatches(Rcpp::CharacterVector lines,
821 const std::string& regex)
822 {
823 Rcpp::Environment base("package:base");
824 Rcpp::Function regexec = base["regexec"];
825 Rcpp::Function regmatches = base["regmatches"];
826 Rcpp::RObject result = regexec(regex, lines);
827 Rcpp::List matches = regmatches(lines, result);
828 return matches;
829 }
830
831 template <typename Stream>
readFile(const std::string & file,Stream & os)832 void readFile(const std::string& file, Stream& os) {
833 std::ifstream ifs(file.c_str());
834 if (ifs.fail())
835 throw Rcpp::file_io_error(file); // #nocov
836 os << ifs.rdbuf();
837 ifs.close();
838 }
839
840 template <typename Collection>
readLines(std::istream & is,Collection * pLines)841 void readLines(std::istream& is, Collection* pLines) {
842 pLines->clear();
843 std::string line;
844 while(std::getline(is, line)) {
845 // strip \r (for the case of windows line terminators on posix)
846 if (line.length() > 0 && *line.rbegin() == '\r')
847 line.erase(line.length()-1, 1);
848 stripTrailingLineComments(&line);
849 pLines->push_back(line);
850 }
851 }
852
addUniqueDependency(Rcpp::CharacterVector include,std::vector<FileInfo> * pDependencies)853 bool addUniqueDependency(Rcpp::CharacterVector include,
854 std::vector<FileInfo>* pDependencies) {
855
856 // return false if we already have this include
857 std::string path = Rcpp::as<std::string>(include);
858 for (size_t i = 0; i<pDependencies->size(); ++i) {
859 if (pDependencies->at(i).path() == path)
860 return false;
861 }
862
863 // add it and return true
864 pDependencies->push_back(FileInfo(path));
865 return true;
866 }
867
parseSourceDependencies(const std::string & sourceFile,std::vector<FileInfo> * pDependencies)868 void parseSourceDependencies(const std::string& sourceFile,
869 std::vector<FileInfo>* pDependencies) {
870
871 // import R functions
872 Rcpp::Environment baseEnv = Rcpp::Environment::base_env();
873 Rcpp::Function dirname = baseEnv["dirname"];
874 Rcpp::Function filepath = baseEnv["file.path"];
875 Rcpp::Function normalizePath = baseEnv["normalizePath"];
876 Rcpp::Function fileExists = baseEnv["file.exists"];
877 Rcpp::Environment toolsEnv = Rcpp::Environment::namespace_env(
878 "tools");
879 Rcpp::Function filePathSansExt = toolsEnv["file_path_sans_ext"];
880
881 // get the path to the source file's directory
882 Rcpp::CharacterVector sourceDir = dirname(sourceFile);
883
884 // read the source file into a buffer
885 std::stringstream buffer;
886 readFile(sourceFile, buffer);
887
888 // Now read into a list of strings (which we can pass to regexec)
889 // First read into a std::deque (which will handle lots of append
890 // operations efficiently) then copy into an R chracter vector
891 std::deque<std::string> lines;
892 readLines(buffer, &lines);
893 Rcpp::CharacterVector linesVector = Rcpp::wrap(lines);
894
895 // look for local includes
896 Rcpp::List matches = regexMatches(
897 linesVector, "^\\s*#include\\s*\"([^\"]+)\"\\s*$");
898
899 // accumulate local includes (skip commented sections)
900 CommentState commentState;
901 std::vector<FileInfo> newDependencies;
902 for (int i = 0; i<matches.size(); i++) {
903 std::string line = lines[i];
904 commentState.submitLine(line);
905 if (!commentState.inComment()) {
906 // get the match
907 const Rcpp::CharacterVector match = matches[i];
908 if (match.size() == 2) {
909 // compose a full file path for the match
910 Rcpp::CharacterVector include =
911 filepath(sourceDir, std::string(match[1]));
912 // if it exists then normalize and add to our list
913 LogicalVector exists = fileExists(include);
914 if (exists[0]) {
915 include = normalizePath(include, "/");
916 if (addUniqueDependency(include, pDependencies)) {
917 newDependencies.push_back(
918 FileInfo(Rcpp::as<std::string>(include)));
919 }
920
921 std::vector<std::string> exts;
922 exts.push_back(".cc");
923 exts.push_back(".cpp");
924 for (size_t i = 0; i<exts.size(); ++i) {
925
926 // look for corresponding cpp file and add it
927 std::string file = Rcpp::as<std::string>( // #nocov
928 filePathSansExt(include)) + exts[i];
929
930 exists = fileExists(file);
931 if (exists[0]) {
932 if (addUniqueDependency(file,
933 pDependencies)) {
934 FileInfo fileInfo(file);
935 newDependencies.push_back(fileInfo);
936 }
937 }
938 }
939 }
940 }
941 }
942 }
943
944 // look for dependencies recursively
945 for (size_t i = 0; i<newDependencies.size(); i++) {
946 FileInfo dependency = newDependencies[i];
947 parseSourceDependencies(dependency.path(), pDependencies);
948 }
949 }
950
951 // parse the source dependencies from the passed lines
parseSourceDependencies(std::string sourceFile)952 std::vector<FileInfo> parseSourceDependencies(
953 std::string sourceFile) {
954
955 // normalize source file
956 Rcpp::Environment baseEnv = Rcpp::Environment::base_env();
957 Rcpp::Function normalizePath = baseEnv["normalizePath"];
958 sourceFile = Rcpp::as<std::string>(normalizePath(sourceFile, "/"));
959
960 // parse dependencies
961 std::vector<FileInfo> dependencies;
962 parseSourceDependencies(sourceFile, &dependencies);
963
964 // remove main source file
965 dependencies.erase(std::remove(dependencies.begin(), // #nocov
966 dependencies.end(),
967 FileInfo(sourceFile)),
968 dependencies.end());
969
970 return dependencies;
971 }
972
973 // Parse embedded R code chunks from a file (receives the lines of the
974 // file as a CharcterVector for using with regexec and as a standard
975 // stl vector for traversal/insepection)
parseEmbeddedR(Rcpp::CharacterVector linesVector,const std::deque<std::string> & lines)976 std::vector<std::string> parseEmbeddedR(
977 Rcpp::CharacterVector linesVector,
978 const std::deque<std::string>& lines) {
979 Rcpp::List matches = regexMatches(linesVector,
980 "^\\s*/\\*{3,}\\s*[Rr]\\s*$");
981 bool withinRBlock = false;
982 CommentState commentState;
983 std::vector<std::string> embeddedR;
984
985 for (int i = 0; i<matches.size(); i++) {
986
987 // track comment state
988 std::string line = lines[i];
989 commentState.submitLine(line);
990
991 // is this a line that begins an R code block?
992 const Rcpp::CharacterVector match = matches[i];
993 bool beginRBlock = match.size() > 0;
994
995 // check state and do the right thing
996 if (beginRBlock) {
997 withinRBlock = true; // #nocov
998 }
999 else if (withinRBlock) {
1000 if (commentState.inComment()) // #nocov start
1001 embeddedR.push_back(line);
1002 else
1003 withinRBlock = false; // #nocov end
1004 }
1005 }
1006
1007 return embeddedR;
1008 }
1009
1010 } // anonymous namespace
1011
1012
1013 // Generate a type signature for the function with the provided name
1014 // (type signature == function pointer declaration)
signature(const std::string & name) const1015 std::string Function::signature(const std::string& name) const { // #nocov start
1016
1017 std::ostringstream ostr;
1018
1019 ostr << type() << "(*" << name << ")(";
1020
1021 const std::vector<Argument>& args = arguments();
1022 for (std::size_t i = 0; i<args.size(); i++) {
1023 ostr << args[i].type();
1024 if (i != (args.size()-1))
1025 ostr << ",";
1026 }
1027 ostr << ")";
1028
1029 return ostr.str(); // #nocov end
1030 }
1031
1032
1033 // Parse attribute parameter from parameter text
Param(const std::string & paramText)1034 Param::Param(const std::string& paramText)
1035 {
1036 // parse out name/value pair if there is one
1037 std::string::size_type pos = paramText.find("=") ;
1038 if ( pos != std::string::npos ) {
1039 // name
1040 name_ = paramText.substr(0, pos); // #nocov start
1041 trimWhitespace(&name_);
1042 // value
1043 value_ = paramText.substr(pos + 1) ;
1044 trimWhitespace(&value_);
1045 stripQuotes(&value_); // #nocov end
1046 }
1047 else {
1048 name_ = paramText;
1049 trimWhitespace(&name_);
1050 stripQuotes(&name_);
1051 }
1052 }
1053
1054 // Check if the attribute has a parameter of a paricular name
paramNamed(const std::string & name) const1055 Param Attribute::paramNamed(const std::string& name) const {
1056 for (std::vector<Param>::const_iterator
1057 it = params_.begin(); it != params_.end(); ++it) {
1058 if (it->name() == name) // #nocov
1059 return *it; // #nocov
1060 }
1061 return Param();
1062 }
1063
1064 // Type operator <<
operator <<(std::ostream & os,const Type & type)1065 std::ostream& operator<<(std::ostream& os, const Type& type) {
1066 if (!type.empty()) {
1067 if (type.isConst())
1068 os << "const ";
1069 os << type.name();
1070 if (type.isReference())
1071 os << "&";
1072 }
1073 return os;
1074 }
1075
1076 // Print argument
printArgument(std::ostream & os,const Argument & argument,bool printDefault=true)1077 void printArgument(std::ostream& os,
1078 const Argument& argument,
1079 bool printDefault = true) {
1080 if (!argument.empty()) {
1081 os << argument.type();
1082 if (!argument.name().empty()) {
1083 os << " ";
1084 os << argument.name();
1085 if (printDefault && !argument.defaultValue().empty())
1086 os << " = " << argument.defaultValue(); // #nocov
1087 }
1088 }
1089 }
1090
1091 // Argument operator <<
operator <<(std::ostream & os,const Argument & argument)1092 std::ostream& operator<<(std::ostream& os, const Argument& argument) {// #nocov start
1093 printArgument(os, argument);
1094 return os; // #nocov end
1095 }
1096
1097 // Print function
printFunction(std::ostream & os,const Function & function,bool printArgDefaults=true)1098 void printFunction(std::ostream& os,
1099 const Function& function,
1100 bool printArgDefaults = true) {
1101
1102 if (!function.empty()) {
1103 if (!function.type().empty()) {
1104 os << function.type();
1105 os << " ";
1106 }
1107 os << function.name();
1108 os << "(";
1109 const std::vector<Argument>& arguments = function.arguments();
1110 for (std::size_t i = 0; i<arguments.size(); i++) {
1111 printArgument(os, arguments[i], printArgDefaults);
1112 if (i != (arguments.size()-1))
1113 os << ", ";
1114 }
1115 os << ")";
1116 }
1117 }
1118
1119 // Function operator <<
operator <<(std::ostream & os,const Function & function)1120 std::ostream& operator<<(std::ostream& os, const Function& function) {// #nocov start
1121 printFunction(os, function);
1122 return os;
1123 }
1124
1125 // Param operator <<
operator <<(std::ostream & os,const Param & param)1126 std::ostream& operator<<(std::ostream& os, const Param& param) {
1127 if (!param.empty()) {
1128 os << param.name();
1129 if (!param.value().empty())
1130 os << "=" << param.value();
1131 }
1132 return os;
1133 }
1134
1135 // Attribute operator <<
operator <<(std::ostream & os,const Attribute & attribute)1136 std::ostream& operator<<(std::ostream& os, const Attribute& attribute) {
1137 if (!attribute.empty()) {
1138 os << "[[Rcpp::" << attribute.name();
1139 const std::vector<Param>& params = attribute.params();
1140 if (params.size() > 0) {
1141 os << "(";
1142 for (std::size_t i = 0; i<params.size(); i++) {
1143 os << params[i];
1144 if (i != (params.size()-1))
1145 os << ",";
1146 }
1147 os << ")";
1148 }
1149 os << "]]";
1150
1151 if (!attribute.function().empty())
1152 os << " " << attribute.function();
1153 }
1154 return os; // #nocov end
1155 }
1156
1157 // Parse the attributes from a source file
SourceFileAttributesParser(const std::string & sourceFile,const std::string & packageName,bool parseDependencies)1158 SourceFileAttributesParser::SourceFileAttributesParser(
1159 const std::string& sourceFile,
1160 const std::string& packageName,
1161 bool parseDependencies)
1162 : sourceFile_(sourceFile), hasPackageInit_(false)
1163 {
1164
1165 // transform packageName to valid C++ symbol
1166 std::string packageNameCpp = packageName;
1167 std::replace(packageNameCpp.begin(), packageNameCpp.end(), '.', '_');
1168
1169 // First read the entire file into a std::stringstream so we can check
1170 // it for attributes (we don't want to do any of our more expensive
1171 // processing steps if there are no attributes to parse)
1172 std::stringstream buffer;
1173 readFile(sourceFile_, buffer);
1174 std::string contents = buffer.str();
1175
1176 // Check for attribute signature
1177 if (contents.find("[[Rcpp::") != std::string::npos ||
1178 contents.find("RCPP_MODULE") != std::string::npos ||
1179 contents.find("R_init_" + packageNameCpp) != std::string::npos) {
1180
1181 // Now read into a list of strings (which we can pass to regexec)
1182 // First read into a std::deque (which will handle lots of append
1183 // operations efficiently) then copy into an R character vector
1184 std::deque<std::string> lines;
1185 readLines(buffer, &lines);
1186 lines_ = Rcpp::wrap(lines);
1187
1188 // Scan for attributes
1189 CommentState commentState;
1190 Rcpp::List matches = regexMatches(lines_,
1191 "^\\s*//\\s*\\[\\[Rcpp::(\\w+)(\\(.*?\\))?\\]\\]\\s*$");
1192 for (int i = 0; i<matches.size(); i++) {
1193
1194 // track whether we are in a comment and bail if we are in one
1195 std::string line = lines[i];
1196 commentState.submitLine(line);
1197 if (commentState.inComment())
1198 continue;
1199
1200 // attribute line
1201 const Rcpp::CharacterVector match = matches[i];
1202 if (match.size() > 0) {
1203
1204 // if the match size isn't 3 then regmatches has not behaved
1205 // as expected (it should return a vector of either 0 or 3
1206 // elements). we don't ever expect this to occur but if it
1207 // does let's not crash
1208 if (match.size() != 3)
1209 continue; // #nocov
1210
1211 // add the attribute
1212 Attribute attr = parseAttribute(
1213 Rcpp::as<std::vector<std::string> >(match), i);
1214 attributes_.push_back(attr);
1215 }
1216
1217 // if it's not an attribute line then it could still be a
1218 // line of interest (e.g. roxygen comment)
1219 else {
1220
1221 // save roxygen comments
1222 if (line.find("//'") == 0) {
1223 std::string roxLine = "#" + line.substr(2);
1224 roxygenBuffer_.push_back(roxLine);
1225 }
1226
1227 // a non-roxygen line causes us to clear the roxygen buffer
1228 else if (!roxygenBuffer_.empty()) {
1229 roxygenChunks_.push_back(roxygenBuffer_); // #nocov
1230 roxygenBuffer_.clear(); // #nocov
1231 }
1232 }
1233 }
1234
1235 // Scan for Rcpp modules
1236 commentState.reset();
1237 Rcpp::List modMatches = regexMatches(lines_,
1238 "^\\s*RCPP_MODULE\\s*\\(\\s*(\\w+)\\s*\\).*$");
1239 for (int i = 0; i<modMatches.size(); i++) {
1240
1241 // track whether we are in a comment and bail if we are in one
1242 std::string line = lines[i];
1243 commentState.submitLine(line);
1244 if (commentState.inComment())
1245 continue;
1246
1247 // get the module declaration
1248 Rcpp::CharacterVector match = modMatches[i];
1249 if (match.size() > 0) {
1250 const char * name = match[1];
1251 modules_.push_back(name);
1252 }
1253 }
1254
1255 // Scan for package init function
1256 hasPackageInit_ = false;
1257 commentState.reset();
1258 std::string pkgInit = "R_init_" + packageNameCpp;
1259 Rcpp::List initMatches = regexMatches(lines_, "^[^/]+" + pkgInit + ".*DllInfo.*$");
1260 for (int i = 0; i<initMatches.size(); i++) {
1261
1262 // track whether we are in a comment and bail if we are in one
1263 std::string line = lines[i];
1264 commentState.submitLine(line);
1265 if (commentState.inComment())
1266 continue;
1267
1268 // check for a match
1269 Rcpp::CharacterVector match = initMatches[i];
1270 if (match.size() > 0) {
1271 hasPackageInit_ = true; // #nocov start
1272 break;
1273 } // #nocov end
1274 }
1275
1276 // Parse embedded R
1277 embeddedR_ = parseEmbeddedR(lines_, lines);
1278
1279 // Recursively parse dependencies if requested
1280 if (parseDependencies) {
1281
1282 // get source dependencies
1283 sourceDependencies_ = parseSourceDependencies(sourceFile);
1284
1285 // parse attributes and modules from each dependent file
1286 for (size_t i = 0; i<sourceDependencies_.size(); i++) {
1287
1288 // perform parse
1289 std::string dependency = sourceDependencies_[i].path();
1290 SourceFileAttributesParser parser(dependency, packageName, false);
1291
1292 // copy to base attributes (if it's a new attribute)
1293 for (SourceFileAttributesParser::const_iterator // #nocov start
1294 it = parser.begin(); it != parser.end(); ++it) {
1295 if (std::find(attributes_.begin(),
1296 attributes_.end(),
1297 *it) == attributes_.end()) {
1298 attributes_.push_back(*it); // #nocov end
1299 }
1300 }
1301
1302 // copy to base modules
1303 std::copy(parser.modules().begin(),
1304 parser.modules().end(),
1305 std::back_inserter(modules_));
1306 }
1307 }
1308 }
1309 }
1310
1311 // Parse an attribute from the vector returned by regmatches
parseAttribute(const std::vector<std::string> & match,int lineNumber)1312 Attribute SourceFileAttributesParser::parseAttribute(
1313 const std::vector<std::string>& match,
1314 int lineNumber) {
1315
1316 // Attribute name
1317 std::string name = match[1];
1318
1319 // Warn if this is an unknown attribute
1320 if (!isKnownAttribute(name)) {
1321 attributeWarning("Unrecognized attribute Rcpp::" + name, // #nocov
1322 lineNumber); // #nocov
1323 }
1324
1325 // Extract params if we've got them
1326 std::vector<Param> params;
1327 std::string paramsText = match[2];
1328 if (!paramsText.empty()) {
1329
1330 // we know from the regex that it's enclosed in parens so remove
1331 // trim before we do this just in case someone updates the regex
1332 // to allow for whitespace around the call
1333 trimWhitespace(¶msText);
1334
1335 paramsText = paramsText.substr(1, paramsText.size()-2);
1336
1337 // parse the parameters
1338 params = parseParameters(paramsText);
1339 }
1340
1341 // Extract function signature if this is a function attribute
1342 // and it doesn't appear at the end of the file
1343 Function function;
1344
1345 // special handling for export and init
1346 if (name == kExportAttribute || name == kInitAttribute) {
1347
1348 // parse the function (unless we are at the end of the file in
1349 // which case we print a warning)
1350 if ((lineNumber + 1) < lines_.size())
1351 function = parseFunction(lineNumber + 1);
1352 else
1353 rcppExportWarning("No function found", lineNumber); // #nocov
1354
1355 // validate parameters
1356 for (std::size_t i=0; i<params.size(); i++) {
1357
1358 std::string name = params[i].name(); // #nocov start
1359 std::string value = params[i].value();
1360
1361 // un-named parameter that isn't the first parameter
1362 if (value.empty() && (i > 0)) {
1363 rcppExportWarning("No value specified for parameter '" +
1364 name + "'",
1365 lineNumber);
1366 }
1367 // parameter that isn't name or rng
1368 else if (!value.empty() &&
1369 (name != kExportName) &&
1370 (name != kExportRng) &&
1371 (name != kExportInvisible)) {
1372 rcppExportWarning("Unrecognized parameter '" + name + "'",
1373 lineNumber);
1374 }
1375 // rng that isn't true or false
1376 else if (name == kExportRng) {
1377 if (value != kParamValueFalse &&
1378 value != kParamValueTrue &&
1379 value != kParamValueFALSE &&
1380 value != kParamValueTRUE) {
1381 rcppExportWarning("rng value must be true or false",
1382 lineNumber);
1383 }
1384 }
1385 // invisible that isn't true of false
1386 else if (name == kExportInvisible) {
1387 if (value != kParamValueFalse &&
1388 value != kParamValueTrue &&
1389 value != kParamValueFALSE &&
1390 value != kParamValueTRUE) {
1391 rcppExportWarning("invisible value must be true or false",
1392 lineNumber); // #nocov end
1393 }
1394 }
1395 }
1396 }
1397
1398 // validate interfaces parameter
1399 else if (name == kInterfacesAttribute) {
1400 if (params.empty()) { // #nocov start
1401 rcppInterfacesWarning("No interfaces specified", lineNumber);//
1402 }
1403 else {
1404 for (std::size_t i=0; i<params.size(); i++) {
1405 std::string param = params[i].name();
1406 if (param != kInterfaceR && param != kInterfaceCpp) {
1407 rcppInterfacesWarning(
1408 "Unknown interface '" + param + "'", lineNumber);
1409 } // #nocov end
1410 }
1411 }
1412
1413
1414 }
1415
1416 // Return attribute
1417 Attribute attribute = Attribute(name, params, function, roxygenBuffer_);
1418 roxygenBuffer_.clear();
1419 return attribute;
1420 }
1421
1422 // Parse attribute parameters
parseParameters(const std::string & input)1423 std::vector<Param> SourceFileAttributesParser::parseParameters(
1424 const std::string& input) {
1425
1426 const std::string delimiters(",");
1427
1428 std::vector<Param> params;
1429 std::string::size_type current;
1430 std::string::size_type next = -1;
1431 do { // #nocov
1432 next = input.find_first_not_of(delimiters, next + 1);
1433 if (next == std::string::npos)
1434 break; // #nocov
1435 next -= 1;
1436 current = next + 1;
1437 next = input.find_first_of(delimiters, current);
1438 params.push_back(Param(input.substr(current, next - current)));
1439 } while(next != std::string::npos);
1440
1441 return params;
1442 }
1443
1444 // Parse a function from the specified spot in the source file
parseFunction(size_t lineNumber)1445 Function SourceFileAttributesParser::parseFunction(size_t lineNumber) {
1446
1447 // Establish the text to parse for the signature
1448 std::string signature = parseSignature(lineNumber);
1449 if (signature.empty()) {
1450 rcppExportNoFunctionFoundWarning(lineNumber); // #nocov
1451 return Function(); // #nocov
1452 }
1453
1454 // Start at the end and look for the () that deliniates the arguments
1455 // (bail with an empty result if we can't find them)
1456 std::string::size_type endParenLoc = signature.find_last_of(')');
1457 std::string::size_type beginParenLoc = signature.find_first_of('(');
1458 if (endParenLoc == std::string::npos ||
1459 beginParenLoc == std::string::npos ||
1460 endParenLoc < beginParenLoc) {
1461
1462 rcppExportNoFunctionFoundWarning(lineNumber); // #nocov
1463 return Function(); // #nocov
1464 }
1465
1466 // Find the type and name by scanning backwards for the whitespace that
1467 // delimites the type and name
1468 Type type;
1469 std::string name;
1470 const std::string preambleText = signature.substr(0, beginParenLoc);
1471 for (std::string::const_reverse_iterator
1472 it = preambleText.rbegin(); it != preambleText.rend(); ++it) {
1473 char ch = *it;
1474 if (isWhitespace(ch)) {
1475 if (!name.empty()) {
1476 // we are at the break between type and name so we can also
1477 // extract the type
1478 std::string typeText;
1479 while (++it != preambleText.rend())
1480 typeText.insert(0U, 1U, *it);
1481 type = parseType(typeText);
1482
1483 // break (since we now have the name and the type)
1484 break;
1485 }
1486 else
1487 continue; // #nocov
1488 } else {
1489 name.insert(0U, 1U, ch);
1490 }
1491 }
1492
1493 // If we didn't find a name then bail
1494 if (name.empty()) {
1495 rcppExportNoFunctionFoundWarning(lineNumber); // #nocov
1496 return Function(); // #nocov
1497 }
1498
1499 // If we didn't find a type then bail
1500 if (type.empty()) { // #nocov start
1501 rcppExportWarning("No function return type found", lineNumber);
1502 return Function(); // #nocov end
1503 }
1504
1505 // Now scan for arguments
1506 std::vector<Argument> arguments;
1507 std::string argsText = signature.substr(beginParenLoc + 1,
1508 endParenLoc-beginParenLoc-1);
1509 std::vector<std::string> args = parseArguments(argsText);
1510 for (std::vector<std::string>::const_iterator it =
1511 args.begin(); it != args.end(); ++it) {
1512
1513 // Get argument sans whitespace (bail if the arg is empty)
1514 std::string arg = *it;
1515 trimWhitespace(&arg);
1516 if (arg.empty()) {
1517 // we don't warn here because the compilation will fail anyway
1518 continue; // #nocov
1519 }
1520
1521 // If the argument has an = within it then it has a default value
1522 std::string defaultValue;
1523 std::string::size_type eqPos = arg.find_first_of('=');
1524 if ( (eqPos != std::string::npos) && ((eqPos + 1) < arg.size()) ) {
1525 defaultValue = arg.substr(eqPos+1);
1526 trimWhitespace(&defaultValue);
1527 arg = arg.substr(0, eqPos);
1528 trimWhitespace(&arg);
1529 }
1530
1531 // Scan backwards for whitespace to determine where the type ends
1532 // (we go backwards because whitespace is valid inside the type
1533 // identifier but invalid inside the variable name). Note that if
1534 // there is no whitespace we'll end up taking the whole string,
1535 // which allows us to capture a type with no variable (but note
1536 // we'll ultimately fail to parse types with no variable if they
1537 // have embedded whitespace)
1538 std::string::size_type pos = arg.find_last_of(kWhitespaceChars);
1539
1540 // check for name
1541 std::string name;
1542 if (pos != std::string::npos) {
1543 // insert whitespace if variables are joint with '&'
1544 std::string::size_type ref_pos = arg.substr(pos).find_last_of("&");
1545 if (ref_pos != std::string::npos) {
1546 pos += ref_pos + 1; // #nocov
1547 arg.insert(pos, " "); // #nocov
1548 }
1549
1550 name = arg.substr(pos);
1551 trimWhitespace(&name);
1552 }
1553 if (name.empty()) { // #nocov start
1554 rcppExportInvalidParameterWarning(arg, lineNumber);
1555 return Function(); // #nocov end
1556 }
1557
1558 // check for type string
1559 Type type = parseType(arg.substr(0, pos));
1560 if (type.empty()) { // #nocov start
1561 rcppExportInvalidParameterWarning(arg, lineNumber);
1562 return Function(); // #nocov end
1563 }
1564
1565 // add argument
1566 arguments.push_back(Argument(name, type, defaultValue));
1567 }
1568
1569 return Function(type, name, arguments);
1570 }
1571
1572
1573 // Parse the text of a function signature from the specified line
parseSignature(size_t lineNumber)1574 std::string SourceFileAttributesParser::parseSignature(size_t lineNumber) {
1575
1576 // Look for the signature termination ({ or ; not inside quotes)
1577 // on this line and then subsequent lines if necessary
1578 std::string signature;
1579 for (int i = lineNumber; i<lines_.size(); i++) {
1580 std::string line;
1581 line = lines_[i];
1582 bool insideQuotes = false;
1583 char prevChar = 0;
1584 // scan for { or ; not inside quotes
1585 for (size_t c = 0; c < line.length(); ++c) {
1586 // alias character
1587 char ch = line.at(c);
1588 // update quotes state
1589 if (ch == '"' && prevChar != '\\')
1590 insideQuotes = !insideQuotes;
1591 // found signature termination, append and return
1592 if (!insideQuotes && ((ch == '{') || (ch == ';'))) {
1593 signature.append(line.substr(0, c));
1594 return signature;
1595 }
1596 // record prev char (used to check for escaped quote i.e. \")
1597 prevChar = ch;
1598 }
1599
1600 // if we didn't find a terminator on this line then just append the line
1601 // and move on to the next line
1602 signature.append(line);
1603 signature.push_back(' ');
1604 }
1605
1606 // Not found
1607 return std::string(); // #nocov
1608 }
1609
1610
1611 // Parse arguments from function signature. This is tricky because commas
1612 // are used to delimit arguments but are also valid inside template type
1613 // qualifiers.
parseArguments(const std::string & argText)1614 std::vector<std::string> SourceFileAttributesParser::parseArguments(
1615 const std::string& argText) {
1616
1617 int templateCount = 0;
1618 int parenCount = 0;
1619 std::string currentArg;
1620 std::vector<std::string> args;
1621 char quote = 0;
1622 bool escaped = false;
1623 typedef std::string::const_iterator it_t;
1624 for (it_t it = argText.begin(); it != argText.end(); ++it) {
1625
1626 // Store current character
1627 char ch = *it;
1628
1629 // Ignore quoted strings and character values in single quotes
1630 if ( ! quote && (ch == '"' || ch == '\''))
1631 quote = ch;
1632 else if (quote && ch == quote && ! escaped)
1633 quote = 0;
1634
1635 // Escaped character inside quotes
1636 if (escaped)
1637 escaped = false;
1638 else if (quote && ch == '\\')
1639 escaped = true;
1640
1641 // Detect end of argument declaration
1642 if ( ! quote &&
1643 (ch == ',') &&
1644 (templateCount == 0) &&
1645 (parenCount == 0)) {
1646 args.push_back(currentArg);
1647 currentArg.clear();
1648 continue;
1649 }
1650
1651 // Append current character if not a space at start
1652 if ( ! currentArg.empty() || ch != ' ')
1653 currentArg.push_back(ch);
1654
1655 // Count use of potentially enclosed brackets
1656 if ( ! quote) {
1657 switch(ch) {
1658 case '<':
1659 templateCount++;
1660 break;
1661 case '>':
1662 templateCount--;
1663 break;
1664 case '(': // #nocov start
1665 parenCount++;
1666 break;
1667 case ')':
1668 parenCount--;
1669 break; // #nocov end
1670 }
1671 }
1672 }
1673
1674 if (!currentArg.empty())
1675 args.push_back(currentArg);
1676
1677 return args;
1678 }
1679
parseType(const std::string & text)1680 Type SourceFileAttributesParser::parseType(const std::string& text) {
1681
1682 const std::string constQualifier("const");
1683 const std::string referenceQualifier("&");
1684
1685 // trim whitespace
1686 std::string type = text;
1687 trimWhitespace(&type);
1688
1689 // check for const and reference
1690 bool isConst = false;
1691 bool isReference = false;
1692 if (type.find(constQualifier) == 0) {
1693 isConst = true;
1694 type.erase(0, constQualifier.length());
1695 }
1696
1697 // if the type is now empty (because it was detected as only const)
1698 // then this is an invalid state so we bail
1699 if (type.empty())
1700 return Type(); // #nocov
1701
1702 if (type.find(referenceQualifier) ==
1703 (type.length() - referenceQualifier.length())) {
1704 isReference = true;
1705 type.erase(type.length() - referenceQualifier.length());
1706 }
1707 trimWhitespace(&type);
1708
1709 // if the type is now empty because of some strange parse then bail
1710 if (type.empty())
1711 return Type(); // #nocov
1712
1713 return Type(type, isConst, isReference);
1714 }
1715
1716 // Validation helpers
1717
isKnownAttribute(const std::string & name) const1718 bool SourceFileAttributesParser::isKnownAttribute(const std::string& name)
1719 const {
1720 return name == kExportAttribute ||
1721 name == kInitAttribute ||
1722 name == kDependsAttribute ||
1723 name == kPluginsAttribute ||
1724 name == kInterfacesAttribute;
1725 }
1726
1727 // Print an attribute parsing related warning
attributeWarning(const std::string & message,const std::string & attribute,size_t lineNumber)1728 void SourceFileAttributesParser::attributeWarning( // #nocov start
1729 const std::string& message,
1730 const std::string& attribute,
1731 size_t lineNumber) {
1732
1733 // get basename of source file for warning message
1734 Rcpp::Function basename = Rcpp::Environment::base_env()["basename"];
1735 std::string file = Rcpp::as<std::string>(basename(sourceFile_));
1736
1737 std::ostringstream ostr;
1738 ostr << message;
1739 if (!attribute.empty())
1740 ostr << " for " << attribute << " attribute";
1741 ostr << " at " << file << ":" << lineNumber;
1742
1743 showWarning(ostr.str());
1744 }
1745
attributeWarning(const std::string & message,size_t lineNumber)1746 void SourceFileAttributesParser::attributeWarning(
1747 const std::string& message,
1748 size_t lineNumber) {
1749 attributeWarning(message, "", lineNumber);
1750 }
1751
rcppExportWarning(const std::string & message,size_t lineNumber)1752 void SourceFileAttributesParser::rcppExportWarning(
1753 const std::string& message,
1754 size_t lineNumber) {
1755 attributeWarning(message, "Rcpp::export", lineNumber);
1756 }
1757
rcppExportNoFunctionFoundWarning(size_t lineNumber)1758 void SourceFileAttributesParser::rcppExportNoFunctionFoundWarning(
1759 size_t lineNumber) {
1760 rcppExportWarning("No function found", lineNumber);
1761 }
1762
rcppExportInvalidParameterWarning(const std::string & param,size_t lineNumber)1763 void SourceFileAttributesParser::rcppExportInvalidParameterWarning(
1764 const std::string& param,
1765 size_t lineNumber) {
1766 rcppExportWarning("Invalid parameter: "
1767 "'" + param + "'", lineNumber);
1768 }
1769
rcppInterfacesWarning(const std::string & message,size_t lineNumber)1770 void SourceFileAttributesParser::rcppInterfacesWarning(
1771 const std::string& message,
1772 size_t lineNumber) {
1773 attributeWarning(message + " (valid interfaces are 'r' and 'cpp')",
1774 "Rcpp::interfaces", lineNumber);
1775 } // #nocov end
1776
1777
1778 // Track /* */ comment state
submitLine(const std::string & line)1779 void CommentState::submitLine(const std::string& line) {
1780 std::size_t pos = 0;
1781 while (pos != std::string::npos) {
1782
1783 // check for a // which would invalidate any other token found
1784 std::size_t lineCommentPos = line.find("//", pos);
1785
1786 // look for the next token
1787 std::string token = inComment() ? "*/" : "/*";
1788 pos = line.find(token, pos);
1789
1790 // process the comment token if found
1791 if (pos != std::string::npos) {
1792
1793 // break if the line comment precedes the comment token
1794 if (lineCommentPos != std::string::npos && lineCommentPos < pos)
1795 break; // #nocov
1796
1797 inComment_ = !inComment_;
1798 pos += token.size();
1799 }
1800 }
1801 }
1802
1803 } // namespace attributes
1804 } // namespace Rcpp
1805
1806
1807 /*******************************************************************
1808 * AttributesGen.cpp
1809 *******************************************************************/
1810
1811 namespace Rcpp {
1812 namespace attributes {
1813
1814 // constants
1815 namespace {
1816 const char * const kRcppExportsSuffix = "_RcppExports.h";
1817 const char * const kTrySuffix = "_try";
1818 }
1819
ExportsGenerator(const std::string & targetFile,const std::string & package,const std::string & commentPrefix)1820 ExportsGenerator::ExportsGenerator(const std::string& targetFile,
1821 const std::string& package,
1822 const std::string& commentPrefix)
1823 : targetFile_(targetFile),
1824 package_(package),
1825 packageCpp_(package),
1826 commentPrefix_(commentPrefix),
1827 hasCppInterface_(false) {
1828
1829 // read the existing target file if it exists
1830 if (FileInfo(targetFile_).exists()) {
1831 std::ifstream ifs(targetFile_.c_str()); // #nocov start
1832 if (ifs.fail())
1833 throw Rcpp::file_io_error(targetFile_);
1834 std::stringstream buffer;
1835 buffer << ifs.rdbuf();
1836 existingCode_ = buffer.str(); // #nocov end
1837 }
1838
1839 std::replace(packageCpp_.begin(), packageCpp_.end(), '.', '_');
1840
1841 // see if this is safe to overwite and throw if it isn't
1842 if (!isSafeToOverwrite())
1843 throw Rcpp::file_exists(targetFile_); // #nocov
1844 }
1845
writeFunctions(const SourceFileAttributes & attributes,bool verbose)1846 void ExportsGenerator::writeFunctions(
1847 const SourceFileAttributes& attributes,
1848 bool verbose) {
1849
1850 if (attributes.hasInterface(kInterfaceCpp))
1851 hasCppInterface_ = true; // #nocov
1852
1853 doWriteFunctions(attributes, verbose);
1854 }
1855
1856 // Commit the stream -- is a no-op if the existing code is identical
1857 // to the generated code. Returns true if data was written and false
1858 // if it wasn't (throws exception on io error)
commit(const std::string & preamble)1859 bool ExportsGenerator::commit(const std::string& preamble) {
1860
1861 // get the generated code
1862 std::string code = codeStream_.str();
1863
1864 // if there is no generated code AND the exports file does not
1865 // currently exist then do nothing
1866 if (code.empty() && !FileInfo(targetFile_).exists())
1867 return false; // #nocov
1868
1869 // write header/preamble
1870 std::ostringstream headerStream;
1871 headerStream << commentPrefix_ << " Generated by using "
1872 << "Rcpp::compileAttributes()"
1873 << " -> do not edit by hand" << std::endl;
1874 headerStream << commentPrefix_ << " Generator token: "
1875 << generatorToken() << std::endl << std::endl;
1876 if (!preamble.empty())
1877 headerStream << preamble;
1878
1879 // get generated code and only write it if there was a change
1880 std::string generatedCode = headerStream.str() + code;
1881 if (generatedCode != existingCode_) {
1882 // open the file
1883 std::ofstream ofs(targetFile_.c_str(),
1884 std::ofstream::out | std::ofstream::trunc);
1885 if (ofs.fail())
1886 throw Rcpp::file_io_error(targetFile_); // #nocov
1887
1888 // write generated code and return
1889 ofs << generatedCode;
1890 ofs.close();
1891 return true;
1892 }
1893 else {
1894 return false; // #nocov
1895 }
1896 }
1897
1898 // Remove the generated file entirely
remove()1899 bool ExportsGenerator::remove() {
1900 return removeFile(targetFile_);
1901 }
1902
1903 // Convert a possible dot in package name to underscore as needed for header file
dotNameHelper(const std::string & name) const1904 std::string ExportsGenerator::dotNameHelper(const std::string & name) const {
1905 std::string newname(name);
1906 std::replace(newname.begin(), newname.end(), '.', '_');
1907 return newname;
1908 }
1909
CppExportsGenerator(const std::string & packageDir,const std::string & package,const std::string & fileSep)1910 CppExportsGenerator::CppExportsGenerator(const std::string& packageDir,
1911 const std::string& package,
1912 const std::string& fileSep)
1913 : ExportsGenerator(
1914 packageDir + fileSep + "src" + fileSep + "RcppExports.cpp",
1915 package,
1916 "//")
1917 {
1918 }
1919
doWriteFunctions(const SourceFileAttributes & attributes,bool verbose)1920 void CppExportsGenerator::doWriteFunctions(
1921 const SourceFileAttributes& attributes,
1922 bool verbose) {
1923
1924 // generate functions
1925 generateCpp(ostr(),
1926 attributes,
1927 true,
1928 attributes.hasInterface(kInterfaceCpp),
1929 packageCppPrefix());
1930
1931 // track cppExports, signatures, and native routines (we use these
1932 // at the end to generate the ValidateSignature and RegisterCCallable
1933 // functions, and to generate a package init function with native
1934 // routine registration)
1935 for (SourceFileAttributes::const_iterator // #nocov start
1936 it = attributes.begin(); it != attributes.end(); ++it) {
1937
1938 if (it->isExportedFunction()) {
1939
1940 // add it to the cpp exports list if we are generating
1941 // a C++ interface and it's not hidden
1942 if (attributes.hasInterface(kInterfaceCpp)) {
1943 Function fun = it->function().renamedTo(it->exportedCppName());
1944 if (!fun.isHidden())
1945 cppExports_.push_back(*it);
1946 }
1947
1948 // add it to the native routines list
1949 nativeRoutines_.push_back(*it);
1950 } else if (it->name() == kInitAttribute) {
1951 initFunctions_.push_back(*it);
1952 }
1953 } // #nocov end
1954
1955 // record modules
1956 const std::vector<std::string>& modules = attributes.modules();
1957 modules_.insert(modules_.end(), modules.begin(), modules.end());
1958
1959 // verbose if requested
1960 if (verbose) { // #nocov start
1961 Rcpp::Rcout << "Exports from " << attributes.sourceFile() << ":"
1962 << std::endl;
1963 for (std::vector<Attribute>::const_iterator
1964 it = attributes.begin(); it != attributes.end(); ++it) {
1965 if (it->isExportedFunction())
1966 Rcpp::Rcout << " " << it->function() << std::endl;
1967 }
1968 Rcpp::Rcout << std::endl; // #nocov end
1969 }
1970 }
1971
writeEnd(bool hasPackageInit)1972 void CppExportsGenerator::writeEnd(bool hasPackageInit)
1973 {
1974 // generate a function that can be used to validate exported
1975 // functions and their signatures prior to looking up with
1976 // GetCppCallable (otherwise inconsistent signatures between
1977 // client and library would cause a crash)
1978 if (hasCppInterface()) {
1979
1980 ostr() << std::endl; // #nocov start
1981 ostr() << "// validate"
1982 << " (ensure exported C++ functions exist before "
1983 << "calling them)" << std::endl;
1984 ostr() << "static int " << exportValidationFunctionRegisteredName()
1985 << "(const char* sig) { " << std::endl;
1986 ostr() << " static std::set<std::string> signatures;"
1987 << std::endl;
1988 ostr() << " if (signatures.empty()) {" << std::endl;
1989
1990 for (std::size_t i=0;i<cppExports_.size(); i++) {
1991 const Attribute& attr = cppExports_[i];
1992 ostr() << " signatures.insert(\""
1993 << attr.function().signature(attr.exportedName())
1994 << "\");" << std::endl;
1995 }
1996 ostr() << " }" << std::endl;
1997 ostr() << " return signatures.find(sig) != signatures.end();"
1998 << std::endl;
1999 ostr() << "}" << std::endl;
2000
2001 // generate a function that will register all of our C++
2002 // exports as C-callable from other packages
2003 ostr() << std::endl;
2004 ostr() << "// registerCCallable (register entry points for "
2005 "exported C++ functions)" << std::endl;
2006 ostr() << "RcppExport SEXP " << registerCCallableExportedName()
2007 << "() { " << std::endl;
2008 for (std::size_t i=0;i<cppExports_.size(); i++) {
2009 const Attribute& attr = cppExports_[i];
2010 ostr() << registerCCallable(
2011 4,
2012 attr.exportedName(),
2013 attr.function().name() + kTrySuffix);
2014 ostr() << std::endl;
2015 }
2016 ostr() << registerCCallable(4,
2017 exportValidationFunction(),
2018 exportValidationFunction());
2019 ostr() << std::endl;
2020 ostr() << " return R_NilValue;" << std::endl;
2021 ostr() << "}" << std::endl;
2022 }
2023
2024 // write native routines
2025 if (!hasPackageInit && (!nativeRoutines_.empty() || !modules_.empty() || !initFunctions_.empty())) {
2026
2027 // build list of routines we will register
2028 std::vector<std::string> routineNames;
2029 std::vector<std::size_t> routineArgs;
2030 for (std::size_t i=0;i<nativeRoutines_.size(); i++) {
2031 const Attribute& attr = nativeRoutines_[i];
2032 routineNames.push_back(packageCppPrefix() + "_" + attr.function().name());
2033 routineArgs.push_back(attr.function().arguments().size());
2034 }
2035 std::string kRcppModuleBoot = "_rcpp_module_boot_";
2036 for (std::size_t i=0;i<modules_.size(); i++) {
2037 routineNames.push_back(kRcppModuleBoot + modules_[i]);
2038 routineArgs.push_back(0);
2039 }
2040 if (hasCppInterface()) {
2041 routineNames.push_back(registerCCallableExportedName());
2042 routineArgs.push_back(0);
2043 }
2044
2045 // see if there are additional registrations to perform
2046 Rcpp::Function extraRoutinesFunc = Environment::namespace_env("Rcpp")[".extraRoutineRegistrations"];
2047 List extraRoutines = extraRoutinesFunc(targetFile(), routineNames);
2048 std::vector<std::string> declarations = extraRoutines["declarations"];
2049 std::vector<std::string> callEntries = extraRoutines["call_entries"];
2050
2051 // add declarations for modules
2052 for (std::size_t i=0;i<modules_.size(); i++) {
2053 declarations.push_back("RcppExport SEXP " + kRcppModuleBoot + modules_[i] + "();");
2054 }
2055
2056 // generate declarations
2057 if (declarations.size() > 0) {
2058 ostr() << std::endl;
2059 for (std::size_t i = 0; i<declarations.size(); i++)
2060 ostr() << declarations[i] << std::endl;
2061 }
2062
2063 // generate registration code
2064 ostr() << std::endl;
2065 ostr() << "static const R_CallMethodDef CallEntries[] = {" << std::endl;
2066 for (std::size_t i=0;i<routineNames.size(); i++) {
2067 ostr() << " {\"" << routineNames[i] << "\", " <<
2068 "(DL_FUNC) &" << routineNames[i] << ", " <<
2069 routineArgs[i] << "}," << std::endl;
2070 }
2071 if (callEntries.size() > 0) {
2072 for (std::size_t i = 0; i<callEntries.size(); i++)
2073 ostr() << callEntries[i] << std::endl;
2074 }
2075 ostr() << " {NULL, NULL, 0}" << std::endl;
2076 ostr() << "};" << std::endl;
2077
2078 ostr() << std::endl;
2079
2080 // write prototypes for init functions
2081 for (std::size_t i = 0; i<initFunctions_.size(); i++) {
2082 const Function& function = initFunctions_[i].function();
2083 printFunction(ostr(), function, false);
2084 ostr() << ";" << std::endl;
2085 }
2086
2087 ostr() << "RcppExport void R_init_" << packageCpp() << "(DllInfo *dll) {" << std::endl;
2088 ostr() << " R_registerRoutines(dll, NULL, CallEntries, NULL, NULL);" << std::endl;
2089 ostr() << " R_useDynamicSymbols(dll, FALSE);" << std::endl;
2090 // call init functions
2091 for (std::size_t i = 0; i<initFunctions_.size(); i++) {
2092 const Function& function = initFunctions_[i].function();
2093 ostr() << " " << function.name() << "(dll);" << std::endl;
2094 }
2095 ostr() << "}" << std::endl;
2096 }
2097
2098 // warn if both a hand-written package init function and Rcpp::init are used
2099 if (hasPackageInit && !initFunctions_.empty()) {
2100 showWarning("[[Rcpp::init]] attribute used in a package with an explicit "
2101 "R_init function (Rcpp::init functions will not be called)");
2102 }
2103 }
2104
registerCCallable(size_t indent,const std::string & exportedName,const std::string & name) const2105 std::string CppExportsGenerator::registerCCallable(
2106 size_t indent,
2107 const std::string& exportedName,
2108 const std::string& name) const {
2109 std::ostringstream ostr;
2110 std::string indentStr(indent, ' ');
2111 ostr << indentStr << "R_RegisterCCallable(\"" << package() << "\", "
2112 << "\"" << packageCppPrefix() << "_" << exportedName << "\", "
2113 << "(DL_FUNC)" << packageCppPrefix() << "_" << name << ");";
2114 return ostr.str(); // #nocov end
2115 }
2116
commit(const std::vector<std::string> & includes)2117 bool CppExportsGenerator::commit(const std::vector<std::string>& includes) {
2118
2119 // includes
2120 std::ostringstream ostr;
2121 if (!includes.empty()) {
2122 for (std::size_t i=0;i<includes.size(); i++)
2123 ostr << includes[i] << std::endl;
2124 }
2125 if (hasCppInterface()) {
2126 ostr << "#include <string>" << std::endl; // #nocov
2127 ostr << "#include <set>" << std::endl; // #nocov
2128 }
2129 ostr << std::endl;
2130
2131 // always bring in Rcpp
2132 ostr << "using namespace Rcpp;" << std::endl << std::endl;
2133 // initialize references to global Rostreams
2134 initializeGlobals(ostr);
2135
2136 // commit with preamble
2137 return ExportsGenerator::commit(ostr.str());
2138 }
2139
CppExportsIncludeGenerator(const std::string & packageDir,const std::string & package,const std::string & fileSep)2140 CppExportsIncludeGenerator::CppExportsIncludeGenerator(
2141 const std::string& packageDir,
2142 const std::string& package,
2143 const std::string& fileSep)
2144 : ExportsGenerator(
2145 packageDir + fileSep + "inst" + fileSep + "include" +
2146 fileSep + dotNameHelper(package) + kRcppExportsSuffix,
2147 package,
2148 "//")
2149 {
2150 includeDir_ = packageDir + fileSep + "inst" + fileSep + "include";
2151 }
2152
writeBegin()2153 void CppExportsIncludeGenerator::writeBegin() {
2154
2155 ostr() << "namespace " << packageCpp() << " {"
2156 << std::endl << std::endl;
2157
2158 // Import Rcpp into this namespace. This allows declarations to
2159 // be written without fully qualifying all Rcpp types. The only
2160 // negative side-effect is that when this package's namespace
2161 // is imported it will also pull in Rcpp. However since this is
2162 // opt-in and represents a general desire to do namespace aliasing
2163 // this seems okay
2164 ostr() << " using namespace Rcpp;" << std::endl << std::endl;
2165
2166 // Write our export validation helper function. Putting it in
2167 // an anonymous namespace will hide it from callers and give
2168 // it per-translation unit linkage
2169 ostr() << " namespace {" << std::endl;
2170 ostr() << " void validateSignature(const char* sig) {"
2171 << std::endl;
2172 ostr() << " Rcpp::Function require = "
2173 << "Rcpp::Environment::base_env()[\"require\"];"
2174 << std::endl;
2175 ostr() << " require(\"" << package() << "\", "
2176 << "Rcpp::Named(\"quietly\") = true);"
2177 << std::endl;
2178
2179 std::string validate = "validate";
2180 std::string fnType = "Ptr_" + validate;
2181 ostr() << " typedef int(*" << fnType << ")(const char*);"
2182 << std::endl;
2183
2184 std::string ptrName = "p_" + validate;
2185 ostr() << " static " << fnType << " " << ptrName << " = "
2186 << "(" << fnType << ")" << std::endl
2187 << " "
2188 << getCCallable(exportValidationFunctionRegisteredName())
2189 << ";" << std::endl;
2190 ostr() << " if (!" << ptrName << "(sig)) {" << std::endl;
2191 ostr() << " throw Rcpp::function_not_exported("
2192 << std::endl
2193 << " "
2194 << "\"C++ function with signature '\" + std::string(sig) + \"' not found in " << package()
2195 << "\");" << std::endl;
2196 ostr() << " }" << std::endl;
2197 ostr() << " }" << std::endl;
2198
2199 ostr() << " }" << std::endl << std::endl;
2200 }
2201
doWriteFunctions(const SourceFileAttributes & attributes,bool)2202 void CppExportsIncludeGenerator::doWriteFunctions(
2203 const SourceFileAttributes& attributes,
2204 bool) {
2205
2206 // don't write anything if there is no C++ interface
2207 if (!attributes.hasInterface(kInterfaceCpp))
2208 return;
2209
2210 for(std::vector<Attribute>::const_iterator // #nocov start
2211 it = attributes.begin(); it != attributes.end(); ++it) {
2212
2213 if (it->isExportedFunction()) {
2214
2215 Function function =
2216 it->function().renamedTo(it->exportedCppName());
2217
2218 // if it's hidden then don't generate a C++ interface
2219 if (function.isHidden())
2220 continue;
2221
2222 ostr() << " inline " << function << " {"
2223 << std::endl;
2224
2225 std::string fnType = "Ptr_" + function.name();
2226 ostr() << " typedef SEXP(*" << fnType << ")(";
2227 for (size_t i=0; i<function.arguments().size(); i++) {
2228 ostr() << "SEXP";
2229 if (i != (function.arguments().size()-1))
2230 ostr() << ",";
2231 }
2232 ostr() << ");" << std::endl;
2233
2234 std::string ptrName = "p_" + function.name();
2235 ostr() << " static " << fnType << " "
2236 << ptrName << " = NULL;"
2237 << std::endl;
2238 ostr() << " if (" << ptrName << " == NULL) {"
2239 << std::endl;
2240 ostr() << " validateSignature"
2241 << "(\"" << function.signature() << "\");"
2242 << std::endl;
2243 ostr() << " " << ptrName << " = "
2244 << "(" << fnType << ")"
2245 << getCCallable(packageCppPrefix() + "_" + function.name()) << ";"
2246 << std::endl;
2247 ostr() << " }" << std::endl;
2248 ostr() << " RObject rcpp_result_gen;" << std::endl;
2249 ostr() << " {" << std::endl;
2250 if (it->rng())
2251 ostr() << " RNGScope RCPP_rngScope_gen;" << std::endl;
2252 ostr() << " rcpp_result_gen = " << ptrName << "(";
2253
2254 const std::vector<Argument>& args = function.arguments();
2255 for (std::size_t i = 0; i<args.size(); i++) {
2256 ostr() << "Shield<SEXP>(Rcpp::wrap(" << args[i].name() << "))";
2257 if (i != (args.size()-1))
2258 ostr() << ", ";
2259 }
2260
2261 ostr() << ");" << std::endl;
2262 ostr() << " }" << std::endl;
2263
2264 ostr() << " if (rcpp_result_gen.inherits(\"interrupted-error\"))"
2265 << std::endl
2266 << " throw Rcpp::internal::InterruptedException();"
2267 << std::endl;
2268 ostr() << " if (Rcpp::internal::isLongjumpSentinel(rcpp_result_gen))"
2269 << std::endl
2270 << " throw Rcpp::LongjumpException(rcpp_result_gen);"
2271 << std::endl;
2272 ostr() << " if (rcpp_result_gen.inherits(\"try-error\"))"
2273 << std::endl
2274 << " throw Rcpp::exception(Rcpp::as<std::string>("
2275 << "rcpp_result_gen).c_str());"
2276 << std::endl;
2277 if (!function.type().isVoid()) {
2278 ostr() << " return Rcpp::as<" << function.type() << " >"
2279 << "(rcpp_result_gen);" << std::endl;
2280 }
2281
2282 ostr() << " }" << std::endl << std::endl; // #nocov end
2283 }
2284 }
2285 }
2286
writeEnd(bool)2287 void CppExportsIncludeGenerator::writeEnd(bool) {
2288 ostr() << "}" << std::endl;
2289 ostr() << std::endl;
2290 ostr() << "#endif // " << getHeaderGuard() << std::endl;
2291 }
2292
commit(const std::vector<std::string> & includes)2293 bool CppExportsIncludeGenerator::commit(
2294 const std::vector<std::string>& includes) {
2295
2296 if (hasCppInterface()) {
2297
2298 // create the include dir if necessary
2299 createDirectory(includeDir_); // #nocov start
2300
2301 // generate preamble
2302 std::ostringstream ostr;
2303
2304 // header guard
2305 std::string guard = getHeaderGuard();
2306 ostr << "#ifndef " << guard << std::endl;
2307 ostr << "#define " << guard << std::endl << std::endl;
2308
2309 // includes
2310 if (!includes.empty()) {
2311 for (std::size_t i=0;i<includes.size(); i++)
2312 {
2313 // some special processing is required here. we exclude
2314 // the package header file (since it includes this file)
2315 // and we transorm _types includes into local includes
2316 std::string preamble = "#include \"../inst/include/";
2317 std::string pkgInclude = preamble + packageCpp() + ".h\"";
2318 if (includes[i] == pkgInclude)
2319 continue;
2320
2321 // check for _types
2322 std::string typesInclude = preamble + packageCpp() + "_types.h";
2323 if (includes[i].find(typesInclude) != std::string::npos)
2324 {
2325 std::string include = "#include \"" +
2326 includes[i].substr(preamble.length());
2327 ostr << include << std::endl;
2328 }
2329 else
2330 {
2331 ostr << includes[i] << std::endl;
2332 }
2333 }
2334 ostr << std::endl;
2335 }
2336
2337 // commit with preamble
2338 return ExportsGenerator::commit(ostr.str()); // #nocov end
2339 }
2340 else {
2341 return ExportsGenerator::remove();
2342 }
2343 }
2344
getCCallable(const std::string & function) const2345 std::string CppExportsIncludeGenerator::getCCallable(
2346 const std::string& function) const {
2347 std::ostringstream ostr;
2348 ostr << "R_GetCCallable"
2349 << "(\"" << package() << "\", "
2350 << "\"" << function << "\")";
2351 return ostr.str();
2352 }
2353
getHeaderGuard() const2354 std::string CppExportsIncludeGenerator::getHeaderGuard() const {
2355 return "RCPP_" + packageCpp() + "_RCPPEXPORTS_H_GEN_";
2356 }
2357
CppPackageIncludeGenerator(const std::string & packageDir,const std::string & package,const std::string & fileSep)2358 CppPackageIncludeGenerator::CppPackageIncludeGenerator(
2359 const std::string& packageDir,
2360 const std::string& package,
2361 const std::string& fileSep)
2362 : ExportsGenerator(
2363 packageDir + fileSep + "inst" + fileSep + "include" +
2364 fileSep + dotNameHelper(package) + ".h",
2365 package,
2366 "//")
2367 {
2368 includeDir_ = packageDir + fileSep + "inst" + fileSep + "include";
2369 }
2370
writeEnd(bool)2371 void CppPackageIncludeGenerator::writeEnd(bool) {
2372 if (hasCppInterface()) {
2373 // header guard
2374 std::string guard = getHeaderGuard(); // #nocov start
2375 ostr() << "#ifndef " << guard << std::endl;
2376 ostr() << "#define " << guard << std::endl << std::endl;
2377 ostr() << "#include \"" << packageCpp() << kRcppExportsSuffix
2378 << "\"" << std::endl;
2379
2380 ostr() << std::endl;
2381 ostr() << "#endif // " << getHeaderGuard() << std::endl; // #nocov end
2382 }
2383 }
2384
commit(const std::vector<std::string> &)2385 bool CppPackageIncludeGenerator::commit(const std::vector<std::string>&) {
2386 if (hasCppInterface()) {
2387
2388 // create the include dir if necessary
2389 createDirectory(includeDir_); // #nocov
2390
2391 // commit
2392 return ExportsGenerator::commit(); // #nocov
2393 }
2394 else {
2395 return ExportsGenerator::remove();
2396 }
2397 }
2398
getHeaderGuard() const2399 std::string CppPackageIncludeGenerator::getHeaderGuard() const { // #nocov
2400 return "RCPP_" + packageCpp() + "_H_GEN_"; // #nocov
2401 }
2402
RExportsGenerator(const std::string & packageDir,const std::string & package,bool registration,const std::string & fileSep)2403 RExportsGenerator::RExportsGenerator(const std::string& packageDir,
2404 const std::string& package,
2405 bool registration,
2406 const std::string& fileSep)
2407 : ExportsGenerator(
2408 packageDir + fileSep + "R" + fileSep + "RcppExports.R",
2409 package,
2410 "#"),
2411 registration_(registration)
2412 {
2413 }
2414
doWriteFunctions(const SourceFileAttributes & attributes,bool)2415 void RExportsGenerator::doWriteFunctions(
2416 const SourceFileAttributes& attributes,
2417 bool) {
2418 // write standalone roxygen chunks
2419 const std::vector<std::vector<std::string> >& roxygenChunks =
2420 attributes.roxygenChunks();
2421 for (std::size_t i = 0; i<roxygenChunks.size(); i++) {
2422 const std::vector<std::string>& chunk = roxygenChunks[i]; // #nocov start
2423 for (std::size_t l = 0; l < chunk.size(); l++)
2424 ostr() << chunk[l] << std::endl;
2425 ostr() << "NULL" << std::endl << std::endl; // #nocov end
2426 }
2427
2428 // write exported functions
2429 if (attributes.hasInterface(kInterfaceR)) {
2430 // process each attribute
2431 for(std::vector<Attribute>::const_iterator
2432 it = attributes.begin(); it != attributes.end(); ++it) {
2433
2434 // alias the attribute and function (bail if not export)
2435 const Attribute& attribute = *it;
2436 if (!attribute.isExportedFunction())
2437 continue; // #nocov
2438 const Function& function = attribute.function();
2439
2440 // print roxygen lines
2441 for (size_t i=0; i<attribute.roxygen().size(); i++)
2442 ostr() << attribute.roxygen()[i] << std::endl; // #nocov
2443
2444 // build the parameter list
2445 std::string args = generateRArgList(function);
2446
2447 // determine the function name
2448 std::string name = attribute.exportedName();
2449
2450 // determine if return invisible
2451 bool isInvisibleOrVoid = function.type().isVoid() || attribute.invisible();
2452
2453 // write the function
2454 ostr() << name << " <- function(" << args << ") {"
2455 << std::endl;
2456 ostr() << " ";
2457 if (isInvisibleOrVoid)
2458 ostr() << "invisible("; // #nocov
2459 ostr() << ".Call(";
2460 if (!registration_)
2461 ostr() << "'"; // #nocov
2462 else
2463 ostr() << "`";
2464 ostr() << packageCppPrefix() << "_" << function.name();
2465 if (!registration_)
2466 ostr() << "', " << "PACKAGE = '" << package() << "'"; // #nocov
2467 else
2468 ostr() << "`";
2469
2470 // add arguments
2471 const std::vector<Argument>& arguments = function.arguments();
2472 for (size_t i = 0; i<arguments.size(); i++)
2473 ostr() << ", " << arguments[i].name(); // #nocov
2474 ostr() << ")";
2475 if (isInvisibleOrVoid)
2476 ostr() << ")"; // #nocov
2477 ostr() << std::endl;
2478
2479 ostr() << "}" << std::endl << std::endl;
2480 }
2481 }
2482 }
2483
writeEnd(bool)2484 void RExportsGenerator::writeEnd(bool) {
2485 if (hasCppInterface()) { // #nocov start
2486 // register all C-callable functions
2487 ostr() << "# Register entry points for exported C++ functions"
2488 << std::endl;
2489 ostr() << "methods::setLoadAction(function(ns) {" << std::endl;
2490 ostr() << " .Call('" << registerCCallableExportedName()
2491 << "', PACKAGE = '" << package() << "')"
2492 << std::endl << "})" << std::endl; // #nocov end
2493 }
2494 }
2495
commit(const std::vector<std::string> &)2496 bool RExportsGenerator::commit(const std::vector<std::string>&) {
2497 return ExportsGenerator::commit();
2498 }
2499
~ExportsGenerators()2500 ExportsGenerators::~ExportsGenerators() {
2501 try {
2502 for(Itr it = generators_.begin(); it != generators_.end(); ++it)
2503 delete *it;
2504 generators_.clear();
2505 }
2506 catch(...) {}
2507 }
2508
add(ExportsGenerator * pGenerator)2509 void ExportsGenerators::add(ExportsGenerator* pGenerator) {
2510 generators_.push_back(pGenerator);
2511 }
2512
writeBegin()2513 void ExportsGenerators::writeBegin() {
2514 for(Itr it = generators_.begin(); it != generators_.end(); ++it)
2515 (*it)->writeBegin();
2516 }
2517
writeFunctions(const SourceFileAttributes & attributes,bool verbose)2518 void ExportsGenerators::writeFunctions(
2519 const SourceFileAttributes& attributes,
2520 bool verbose) {
2521 for(Itr it = generators_.begin(); it != generators_.end(); ++it)
2522 (*it)->writeFunctions(attributes, verbose);
2523 }
2524
writeEnd(bool hasPackageInit)2525 void ExportsGenerators::writeEnd(bool hasPackageInit) {
2526 for(Itr it = generators_.begin(); it != generators_.end(); ++it)
2527 (*it)->writeEnd(hasPackageInit);
2528 }
2529
2530 // Commit and return a list of the files that were updated
commit(const std::vector<std::string> & includes)2531 std::vector<std::string> ExportsGenerators::commit(
2532 const std::vector<std::string>& includes) {
2533
2534 std::vector<std::string> updated;
2535
2536 for(Itr it = generators_.begin(); it != generators_.end(); ++it) {
2537 if ((*it)->commit(includes))
2538 updated.push_back((*it)->targetFile());
2539 }
2540
2541 return updated;
2542 }
2543
2544 // Remove and return a list of files that were removed
remove()2545 std::vector<std::string> ExportsGenerators::remove() { // #nocov start
2546 std::vector<std::string> removed;
2547 for(Itr it = generators_.begin(); it != generators_.end(); ++it) {
2548 if ((*it)->remove())
2549 removed.push_back((*it)->targetFile());
2550 }
2551 return removed;
2552 }
2553
2554
2555 // Helpers for converting C++ default arguments to R default arguments
2556 namespace {
2557
2558 // convert a C++ numeric argument to an R argument value
2559 // (returns empty string if no conversion is possible)
cppNumericArgToRArg(const std::string & type,const std::string & cppArg)2560 std::string cppNumericArgToRArg(const std::string& type,
2561 const std::string& cppArg) {
2562 // check for a number
2563 double num;
2564 std::stringstream argStream(cppArg);
2565 if ((argStream >> num)) {
2566
2567 // L suffix means return the value literally
2568 if (!argStream.eof()) {
2569 std::string suffix;
2570 argStream >> suffix;
2571 if (argStream.eof() && suffix == "L")
2572 return cppArg;
2573 }
2574
2575 // no decimal and the type isn't explicitly double or
2576 // float means integer
2577 if (cppArg.find('.') == std::string::npos &&
2578 type != "double" && type != "float")
2579 return cppArg + "L";
2580
2581 // otherwise return arg literally
2582 else
2583 return cppArg;
2584 }
2585 else {
2586 return std::string();
2587 }
2588 }
2589
2590 // convert a C++ ::create style argument value to an R argument
2591 // value (returns empty string if no conversion is possible)
cppCreateArgToRArg(const std::string & cppArg)2592 std::string cppCreateArgToRArg(const std::string& cppArg) {
2593
2594 std::string create = "::create";
2595 size_t createLoc = cppArg.find(create);
2596 if (createLoc == std::string::npos ||
2597 ((createLoc + create.length()) >= cppArg.size())) {
2598 return std::string();
2599 }
2600
2601 std::string type = cppArg.substr(0, createLoc);
2602 std::string rcppScope = "Rcpp::";
2603 size_t rcppLoc = type.find(rcppScope);
2604 if (rcppLoc == 0 && type.size() > rcppScope.length())
2605 type = type.substr(rcppScope.length());
2606
2607 std::string args = cppArg.substr(createLoc + create.length());
2608 if (type == "CharacterVector")
2609 return "as.character( c" + args + ")";
2610 if (type == "IntegerVector")
2611 return "as.integer( c" + args + ")";
2612 if (type == "NumericVector")
2613 return "as.numeric( c" + args + ")";
2614 if (type == "LogicalVector")
2615 return "as.logical( c" + args + ")";
2616
2617 return std::string();
2618 }
2619
2620 // convert a C++ Matrix to an R argument (returns empty string
2621 // if no conversion possible)
cppMatrixArgToRArg(const std::string & cppArg)2622 std::string cppMatrixArgToRArg(const std::string& cppArg) {
2623
2624 // look for Matrix
2625 std::string matrix = "Matrix";
2626 size_t matrixLoc = cppArg.find(matrix);
2627 if (matrixLoc == std::string::npos ||
2628 ((matrixLoc + matrix.length()) >= cppArg.size())) {
2629 return std::string();
2630 }
2631
2632 std::string args = cppArg.substr(matrixLoc + matrix.length());
2633 return "matrix" + args; // #nocov end
2634 }
2635
2636 // convert a C++ literal to an R argument (returns empty string
2637 // if no conversion possible)
cppLiteralArgToRArg(const std::string & cppArg)2638 std::string cppLiteralArgToRArg(const std::string& cppArg) {
2639 if (cppArg == "true")
2640 return "TRUE";
2641 else if (cppArg == "false")
2642 return "FALSE";
2643 else if (cppArg == "R_NilValue")
2644 return "NULL";
2645 else if (cppArg == "NA_STRING") // #nocov start
2646 return "NA_character_";
2647 else if (cppArg == "NA_INTEGER")
2648 return "NA_integer_";
2649 else if (cppArg == "NA_LOGICAL")
2650 return "NA_integer_";
2651 else if (cppArg == "NA_REAL")
2652 return "NA_real_";
2653 else
2654 return std::string();
2655 }
2656
2657 // convert an Rcpp container constructor to an R argument
2658 // (returns empty string if no conversion possible)
cppConstructorArgToRArg(const std::string & cppArg)2659 std::string cppConstructorArgToRArg(const std::string& cppArg) {
2660
2661 // map Rcpp containers to R default initializers
2662 static std::map<std::string, std::string> RcppContainerToR;
2663 RcppContainerToR.insert(std::make_pair("NumericVector", "numeric"));
2664 RcppContainerToR.insert(std::make_pair("DoubleVector", "numeric"));
2665 RcppContainerToR.insert(std::make_pair("CharacterVector", "character"));
2666 RcppContainerToR.insert(std::make_pair("IntegerVector", "integer"));
2667 RcppContainerToR.insert(std::make_pair("LogicalVector", "logical"));
2668 RcppContainerToR.insert(std::make_pair("ComplexVector", "complex"));
2669
2670 // for each entry in the map above, see if we find it; if we do,
2671 // return the R version
2672 typedef std::map<std::string, std::string>::const_iterator Iterator;
2673 for (Iterator it = RcppContainerToR.begin(); it != RcppContainerToR.end(); ++it) {
2674 size_t loc = cppArg.find(it->first);
2675 if (loc != std::string::npos) {
2676 return it->second + cppArg.substr(it->first.size(), std::string::npos);
2677 }
2678 }
2679
2680 return std::string(); // #nocov end
2681
2682 }
2683
2684 // convert a C++ argument value to an R argument value (returns empty
2685 // string if no conversion is possible)
cppArgToRArg(const std::string & type,const std::string & cppArg)2686 std::string cppArgToRArg(const std::string& type,
2687 const std::string& cppArg) {
2688
2689 // try for quoted string
2690 if (isQuoted(cppArg))
2691 return cppArg;
2692
2693 // try for literal
2694 std::string rArg = cppLiteralArgToRArg(cppArg);
2695 if (!rArg.empty())
2696 return rArg;
2697
2698 // try for a create arg
2699 rArg = cppCreateArgToRArg(cppArg); // #nocov start
2700 if (!rArg.empty())
2701 return rArg;
2702
2703 // try for a matrix arg
2704 rArg = cppMatrixArgToRArg(cppArg);
2705 if (!rArg.empty())
2706 return rArg;
2707
2708 // try for a numeric arg
2709 rArg = cppNumericArgToRArg(type, cppArg);
2710 if (!rArg.empty())
2711 return rArg;
2712
2713 // try for a constructor arg
2714 rArg = cppConstructorArgToRArg(cppArg);
2715 if (!rArg.empty())
2716 return rArg;
2717
2718 // couldn't parse the arg
2719 return std::string(); // #nocov end
2720 }
2721
2722 } // anonymous namespace
2723
2724 // Generate an R argument list for a function
generateRArgList(const Function & function)2725 std::string generateRArgList(const Function& function) {
2726 std::ostringstream argsOstr;
2727 const std::vector<Argument>& arguments = function.arguments();
2728 for (size_t i = 0; i<arguments.size(); i++) {
2729 const Argument& argument = arguments[i];
2730 argsOstr << argument.name();
2731 if (!argument.defaultValue().empty()) {
2732 std::string rArg = cppArgToRArg(argument.type().name(),
2733 argument.defaultValue());
2734 if (!rArg.empty()) {
2735 argsOstr << " = " << rArg;
2736 } else {
2737 showWarning("Unable to parse C++ default value '" + // #nocov start
2738 argument.defaultValue() + "' for argument "+
2739 argument.name() + " of function " +
2740 function.name()); // #nocov end
2741 }
2742 }
2743
2744 if (i != (arguments.size()-1))
2745 argsOstr << ", ";
2746 }
2747 return argsOstr.str();
2748 }
2749
2750 // Generate the C++ code required to initialize global objects
initializeGlobals(std::ostream & ostr)2751 void initializeGlobals(std::ostream& ostr) {
2752 ostr << "#ifdef RCPP_USE_GLOBAL_ROSTREAM" << std::endl;
2753 ostr << "Rcpp::Rostream<true>& Rcpp::Rcout = Rcpp::Rcpp_cout_get();";
2754 ostr << std::endl;
2755 ostr << "Rcpp::Rostream<false>& Rcpp::Rcerr = Rcpp::Rcpp_cerr_get();";
2756 ostr << std::endl;
2757 ostr << "#endif" << std::endl << std::endl;
2758 }
2759
2760 // Generate the C++ code required to make [[Rcpp::export]] functions
2761 // available as C symbols with SEXP parameters and return
generateCpp(std::ostream & ostr,const SourceFileAttributes & attributes,bool includePrototype,bool cppInterface,const std::string & contextId)2762 void generateCpp(std::ostream& ostr,
2763 const SourceFileAttributes& attributes,
2764 bool includePrototype,
2765 bool cppInterface,
2766 const std::string& contextId) {
2767
2768 // process each attribute
2769 for(std::vector<Attribute>::const_iterator
2770 it = attributes.begin(); it != attributes.end(); ++it) {
2771
2772 // alias the attribute and function (bail if not export)
2773 const Attribute& attribute = *it;
2774 if (!attribute.isExportedFunction())
2775 continue;
2776 const Function& function = attribute.function();
2777
2778 // include prototype if requested
2779 if (includePrototype) {
2780 ostr << "// " << function.name() << std::endl;
2781 printFunction(ostr, function, false);
2782 ostr << ";";
2783 }
2784
2785 // write the C++ callable SEXP-based function (this version
2786 // returns errors via "try-error")
2787 ostr << std::endl;
2788 ostr << (cppInterface ? "static" : "RcppExport");
2789 ostr << " SEXP ";
2790 std::string funcName = contextId + "_" + function.name();
2791 ostr << funcName;
2792 if (cppInterface)
2793 ostr << kTrySuffix; // #nocov
2794 ostr << "(";
2795 std::ostringstream ostrArgs;
2796 const std::vector<Argument>& arguments = function.arguments();
2797 for (size_t i = 0; i<arguments.size(); i++) {
2798 const Argument& argument = arguments[i];
2799 ostrArgs << "SEXP " << argument.name() << "SEXP";
2800 if (i != (arguments.size()-1))
2801 ostrArgs << ", ";
2802 }
2803 std::string args = ostrArgs.str();
2804 ostr << args << ") {" << std::endl;
2805 ostr << "BEGIN_RCPP" << std::endl;
2806 if (!function.type().isVoid())
2807 ostr << " Rcpp::RObject rcpp_result_gen;" << std::endl;
2808 if (!cppInterface && attribute.rng())
2809 ostr << " Rcpp::RNGScope rcpp_rngScope_gen;" << std::endl;
2810 for (size_t i = 0; i<arguments.size(); i++) {
2811 const Argument& argument = arguments[i];
2812
2813 ostr << " Rcpp::traits::input_parameter< "
2814 << argument.type().full_name() << " >::type " << argument.name()
2815 << "(" << argument.name() << "SEXP);" << std::endl;
2816 }
2817
2818 ostr << " ";
2819 if (!function.type().isVoid())
2820 ostr << "rcpp_result_gen = Rcpp::wrap(";
2821 ostr << function.name() << "(";
2822 for (size_t i = 0; i<arguments.size(); i++) {
2823 const Argument& argument = arguments[i];
2824 ostr << argument.name();
2825 if (i != (arguments.size()-1))
2826 ostr << ", ";
2827 }
2828 if (!function.type().isVoid())
2829 ostr << ")";
2830 ostr << ");" << std::endl;
2831
2832 if (!function.type().isVoid())
2833 {
2834 ostr << " return rcpp_result_gen;" << std::endl;
2835 }
2836 else
2837 {
2838 ostr << " return R_NilValue;" << std::endl;
2839 }
2840 ostr << (cppInterface ? "END_RCPP_RETURN_ERROR" : "END_RCPP")
2841 << std::endl;
2842 ostr << "}" << std::endl;
2843
2844 // Now write an R wrapper that returns error via Rf_error
2845 if (cppInterface) {
2846 ostr << "RcppExport SEXP " << funcName << "(" << args << ") {" // #nocov start
2847 << std::endl;
2848 ostr << " SEXP rcpp_result_gen;" << std::endl;
2849 ostr << " {" << std::endl;
2850 if (attribute.rng())
2851 ostr << " Rcpp::RNGScope rcpp_rngScope_gen;" << std::endl;
2852 ostr << " rcpp_result_gen = PROTECT(" << funcName
2853 << kTrySuffix << "(";
2854 for (size_t i = 0; i<arguments.size(); i++) {
2855 const Argument& argument = arguments[i];
2856 ostr << argument.name() << "SEXP";
2857 if (i != (arguments.size()-1))
2858 ostr << ", ";
2859 }
2860 ostr << "));" << std::endl;
2861 ostr << " }" << std::endl;
2862 ostr << " Rboolean rcpp_isInterrupt_gen = Rf_inherits(rcpp_result_gen, \"interrupted-error\");"
2863 << std::endl
2864 << " if (rcpp_isInterrupt_gen) {" << std::endl
2865 << " UNPROTECT(1);" << std::endl
2866 << " Rf_onintr();" << std::endl
2867 << " }" << std::endl
2868 << " bool rcpp_isLongjump_gen = Rcpp::internal::isLongjumpSentinel(rcpp_result_gen);" << std::endl
2869 << " if (rcpp_isLongjump_gen) {" << std::endl
2870 // No need to unprotect before jump
2871 << " Rcpp::internal::resumeJump(rcpp_result_gen);" << std::endl
2872 << " }" << std::endl
2873 << " Rboolean rcpp_isError_gen = Rf_inherits(rcpp_result_gen, \"try-error\");"
2874 << std::endl
2875 << " if (rcpp_isError_gen) {" << std::endl
2876 << " SEXP rcpp_msgSEXP_gen = Rf_asChar(rcpp_result_gen);" << std::endl
2877 << " UNPROTECT(1);" << std::endl
2878 << " Rf_error(CHAR(rcpp_msgSEXP_gen));" << std::endl
2879 << " }" << std::endl
2880 << " UNPROTECT(1);" << std::endl
2881 << " return rcpp_result_gen;" << std::endl
2882 << "}" << std::endl; // #nocov end
2883 }
2884 }
2885 }
2886
2887 } // namespace attributes
2888 } // namespace Rcpp
2889
2890
2891
2892
2893
2894 // provide implementations for util
2895 namespace Rcpp {
2896 namespace attributes {
2897
2898 // Utility class for getting file existence and last modified time
FileInfo(const std::string & path)2899 FileInfo::FileInfo(const std::string& path)
2900 : path_(path), exists_(false), lastModified_(0)
2901 {
2902 #ifdef _WIN32
2903 struct _stat buffer;
2904 int result = _stat(path.c_str(), &buffer);
2905 #else
2906 struct stat buffer;
2907 int result = stat(path.c_str(), &buffer);
2908 #endif
2909 if (result != 0) {
2910 if (errno == ENOENT)
2911 exists_ = false;
2912 else
2913 throw Rcpp::file_io_error(errno, path); // #nocov
2914 } else {
2915 exists_ = true;
2916 lastModified_ = static_cast<double>(buffer.st_mtime);
2917 }
2918 }
2919
2920 // Remove a file (call back into R for this)
removeFile(const std::string & path)2921 bool removeFile(const std::string& path) {
2922 if (FileInfo(path).exists()) {
2923 Rcpp::Function rm = Rcpp::Environment::base_env()["file.remove"]; // #nocov start
2924 rm(path);
2925 return true; // #nocov end
2926 }
2927 else {
2928 return false;
2929 }
2930 }
2931
2932 // Recursively create a directory (call back into R for this)
createDirectory(const std::string & path)2933 void createDirectory(const std::string& path) { // #nocov start
2934 if (!FileInfo(path).exists()) {
2935 Rcpp::Function mkdir = Rcpp::Environment::base_env()["dir.create"];
2936 mkdir(path, Rcpp::Named("recursive") = true);
2937 }
2938 } // #nocov end
2939
2940 // Known whitespace chars
2941 const char * const kWhitespaceChars = " \f\n\r\t\v";
2942
2943 // Query whether a character is whitespace
isWhitespace(char ch)2944 bool isWhitespace(char ch) {
2945 return std::strchr(kWhitespaceChars, ch) != NULL;
2946 }
2947
2948 // Remove trailing line comments -- ie, find comments that don't begin
2949 // a line, and remove them. We avoid stripping attributes.
stripTrailingLineComments(std::string * pStr)2950 void stripTrailingLineComments(std::string* pStr) {
2951
2952 if (pStr->empty()) return;
2953
2954 size_t len = pStr->length();
2955 bool inString = false;
2956 size_t idx = 0;
2957
2958 // if this is an roxygen comment, then bail
2959 if (isRoxygenCpp(*pStr)) return;
2960
2961 // skip over initial whitespace
2962 idx = pStr->find_first_not_of(kWhitespaceChars);
2963 if (idx == std::string::npos) return;
2964
2965 // skip over a first comment
2966 if (idx + 1 < len && pStr->at(idx) == '/' && pStr->at(idx + 1) == '/') {
2967 idx = idx + 2;
2968 }
2969
2970 // since we are searching for "//", we iterate up to 2nd last character
2971 while (idx < len - 1) {
2972
2973 if (inString) {
2974 if (pStr->at(idx) == '"' && pStr->at(idx - 1) != '\\') {
2975 inString = false;
2976 }
2977 } else {
2978 if (pStr->at(idx) == '"') {
2979 inString = true;
2980 }
2981 }
2982
2983 if (!inString &&
2984 pStr->at(idx) == '/' &&
2985 pStr->at(idx + 1) == '/') {
2986 pStr->erase(idx);
2987 return;
2988 }
2989 ++idx;
2990 }
2991 }
2992
2993 // Trim a string
trimWhitespace(std::string * pStr)2994 void trimWhitespace(std::string* pStr) {
2995
2996 // skip empty case
2997 if (pStr->empty())
2998 return; // #nocov
2999
3000 // trim right
3001 std::string::size_type pos = pStr->find_last_not_of(kWhitespaceChars);
3002 if (pos != std::string::npos)
3003 pStr->erase(pos + 1);
3004
3005 // trim left
3006 pos = pStr->find_first_not_of(kWhitespaceChars);
3007 pStr->erase(0, pos);
3008 }
3009
3010 // Strip balanced quotes from around a string (assumes already trimmed)
stripQuotes(std::string * pStr)3011 void stripQuotes(std::string* pStr) {
3012 if (pStr->length() < 2)
3013 return;
3014 char quote = *(pStr->begin());
3015 if ( (quote == '\'' || quote == '\"') && (*(pStr->rbegin()) == quote) )
3016 *pStr = pStr->substr(1, pStr->length()-2); // #nocov
3017 }
3018
3019 // is the passed string quoted?
isQuoted(const std::string & str)3020 bool isQuoted(const std::string& str) {
3021 if (str.length() < 2)
3022 return false; // #nocov
3023 char quote = *(str.begin());
3024 return (quote == '\'' || quote == '\"') && (*(str.rbegin()) == quote);
3025 }
3026
3027 // does a string end with another string?
endsWith(const std::string & str,const std::string & suffix)3028 bool endsWith(const std::string& str, const std::string& suffix)
3029 {
3030 return str.size() >= suffix.size() &&
3031 str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0;
3032 }
3033
3034 // show a warning message
showWarning(const std::string & msg)3035 void showWarning(const std::string& msg) { // #nocov start
3036 Rcpp::Function warning = Rcpp::Environment::base_env()["warning"];
3037 warning(msg, Rcpp::Named("call.") = false);
3038 } // #nocov end
3039
isRoxygenCpp(const std::string & str)3040 bool isRoxygenCpp(const std::string& str) {
3041 size_t len = str.length();
3042 if (len < 3) return false;
3043 size_t idx = str.find_first_not_of(kWhitespaceChars);
3044 if (idx == std::string::npos) return false;
3045
3046 // make sure there are characters to check
3047 if (len - 2 < idx) return false;
3048
3049 if (str[idx] == '/' &&
3050 str[idx + 1] == '/' &&
3051 str[idx + 2] == '\'') {
3052 return true;
3053 }
3054
3055 return false;
3056
3057 }
3058
3059 } // namespace attributes
3060 } // namespace Rcpp
3061
3062
3063 /*******************************************************************
3064 * Attributes.cpp
3065 *******************************************************************/
3066
3067 using namespace Rcpp::attributes;
3068
3069 // Implementation helpers for sourceCppContext
3070 namespace {
3071
3072 // Class that manages generation of source code for the sourceCpp dynlib
3073 class SourceCppDynlib {
3074 public:
SourceCppDynlib()3075 SourceCppDynlib() {}
3076
SourceCppDynlib(const std::string & cacheDir,const std::string & cppSourcePath,Rcpp::List platform)3077 SourceCppDynlib(const std::string& cacheDir,
3078 const std::string& cppSourcePath,
3079 Rcpp::List platform)
3080 : cppSourcePath_(cppSourcePath)
3081
3082 {
3083 // get cpp source file info
3084 FileInfo cppSourceFilenameInfo(cppSourcePath_);
3085 if (!cppSourceFilenameInfo.exists())
3086 throw Rcpp::file_not_found(cppSourcePath_); // #nocov
3087
3088 // record the base name of the source file
3089 Rcpp::Function basename = Rcpp::Environment::base_env()["basename"];
3090 cppSourceFilename_ = Rcpp::as<std::string>(basename(cppSourcePath_));
3091
3092 // get platform info
3093 fileSep_ = Rcpp::as<std::string>(platform["file.sep"]);
3094 dynlibExt_ = Rcpp::as<std::string>(platform["dynlib.ext"]);
3095
3096 // generate temp directory
3097 Rcpp::Function tempfile = Rcpp::Environment::base_env()["tempfile"];
3098 buildDirectory_ = Rcpp::as<std::string>(tempfile("sourcecpp_", cacheDir));
3099 std::replace(buildDirectory_.begin(), buildDirectory_.end(), '\\', '/');
3100 Rcpp::Function dircreate = Rcpp::Environment::base_env()["dir.create"];
3101 dircreate(buildDirectory_);
3102
3103 // generate a random context id
3104 contextId_ = "sourceCpp_" + uniqueToken(cacheDir);
3105
3106 // regenerate the source code
3107 regenerateSource(cacheDir);
3108 }
3109
3110 // create from list
SourceCppDynlib(const Rcpp::List & dynlib)3111 explicit SourceCppDynlib(const Rcpp::List& dynlib)
3112 {
3113 using namespace Rcpp;
3114
3115 cppSourcePath_ = as<std::string>(dynlib["cppSourcePath"]);
3116 generatedCpp_ = as<std::string>(dynlib["generatedCpp"]);
3117 cppSourceFilename_ = as<std::string>(dynlib["cppSourceFilename"]);
3118 contextId_ = as<std::string>(dynlib["contextId"]);
3119 buildDirectory_ = as<std::string>(dynlib["buildDirectory"]);
3120 fileSep_ = as<std::string>(dynlib["fileSep"]);
3121 dynlibFilename_ = as<std::string>(dynlib["dynlibFilename"]);
3122 previousDynlibFilename_ = as<std::string>(dynlib["previousDynlibFilename"]);
3123 dynlibExt_ = as<std::string>(dynlib["dynlibExt"]);
3124 exportedFunctions_ = as<std::vector<std::string> >(dynlib["exportedFunctions"]);
3125 modules_ = as<std::vector<std::string> >(dynlib["modules"]);
3126 depends_ = as<std::vector<std::string> >(dynlib["depends"]);
3127 plugins_ = as<std::vector<std::string> >(dynlib["plugins"]);
3128 embeddedR_ = as<std::vector<std::string> >(dynlib["embeddedR"]);
3129 List sourceDependencies = as<List>(dynlib["sourceDependencies"]);
3130 for (R_xlen_t i = 0; i<sourceDependencies.length(); i++) {
3131 List fileInfo = as<List>(sourceDependencies.at(i)); // #nocov
3132 sourceDependencies_.push_back(FileInfo(fileInfo)); // #nocov
3133 }
3134 }
3135
3136 // convert to list
toList() const3137 Rcpp::List toList() const {
3138 using namespace Rcpp;
3139 List dynlib;
3140 dynlib["cppSourcePath"] = cppSourcePath_;
3141 dynlib["generatedCpp"] = generatedCpp_;
3142 dynlib["cppSourceFilename"] = cppSourceFilename_;
3143 dynlib["contextId"] = contextId_;
3144 dynlib["buildDirectory"] = buildDirectory_;
3145 dynlib["fileSep"] = fileSep_;
3146 dynlib["dynlibFilename"] = dynlibFilename_;
3147 dynlib["previousDynlibFilename"] = previousDynlibFilename_;
3148 dynlib["dynlibExt"] = dynlibExt_;
3149 dynlib["exportedFunctions"] = exportedFunctions_;
3150 dynlib["modules"] = modules_;
3151 dynlib["depends"] = depends_;
3152 dynlib["plugins"] = plugins_;
3153 dynlib["embeddedR"] = embeddedR_;
3154 List sourceDependencies;
3155 for (std::size_t i = 0; i<sourceDependencies_.size(); i++) {
3156 FileInfo fileInfo = sourceDependencies_.at(i);
3157 sourceDependencies.push_back(fileInfo.toList());
3158 }
3159 dynlib["sourceDependencies"] = sourceDependencies;
3160
3161 return dynlib;
3162 }
3163
isEmpty() const3164 bool isEmpty() const { return cppSourcePath_.empty(); }
3165
isBuilt() const3166 bool isBuilt() const { return FileInfo(dynlibPath()).exists(); };
3167
isSourceDirty() const3168 bool isSourceDirty() const {
3169 // source file out of date means we're dirty
3170 if (FileInfo(cppSourcePath_).lastModified() >
3171 FileInfo(generatedCppSourcePath()).lastModified())
3172 return true; // #nocov
3173
3174 // no dynlib means we're dirty
3175 if (!FileInfo(dynlibPath()).exists())
3176 return true; // #nocov
3177
3178 // variation in source dependencies means we're dirty
3179 std::vector<FileInfo> sourceDependencies = parseSourceDependencies(
3180 cppSourcePath_);
3181 if (sourceDependencies != sourceDependencies_)
3182 return true; // #nocov
3183
3184 // not dirty
3185 return false;
3186 }
3187
regenerateSource(const std::string & cacheDir)3188 void regenerateSource(const std::string& cacheDir) {
3189
3190 // create new dynlib filename
3191 previousDynlibFilename_ = dynlibFilename_;
3192 dynlibFilename_ = "sourceCpp_" + uniqueToken(cacheDir) + dynlibExt_;
3193
3194 // copy the source file to the build dir
3195 Rcpp::Function filecopy = Rcpp::Environment::base_env()["file.copy"];
3196 filecopy(cppSourcePath_, generatedCppSourcePath(), true);
3197
3198 // parse attributes
3199 SourceFileAttributesParser sourceAttributes(cppSourcePath_, "", true);
3200
3201 // generate cpp for attributes and append them
3202 std::ostringstream ostr;
3203 // always include Rcpp.h in case the user didn't
3204 ostr << std::endl << std::endl;
3205 ostr << "#include <Rcpp.h>" << std::endl;
3206 // initialize references to global Rostreams
3207 initializeGlobals(ostr);
3208 generateCpp(ostr, sourceAttributes, true, false, contextId_);
3209 generatedCpp_ = ostr.str();
3210 std::ofstream cppOfs(generatedCppSourcePath().c_str(),
3211 std::ofstream::out | std::ofstream::app);
3212 if (cppOfs.fail())
3213 throw Rcpp::file_io_error(generatedCppSourcePath()); // #nocov
3214 cppOfs << generatedCpp_;
3215 cppOfs.close();
3216
3217 // generate R for attributes and write it into the build directory
3218 std::ofstream rOfs(generatedRSourcePath().c_str(),
3219 std::ofstream::out | std::ofstream::trunc);
3220 if (rOfs.fail())
3221 throw Rcpp::file_io_error(generatedRSourcePath()); // #nocov
3222
3223 // DLLInfo - hide using . and ensure uniqueness using contextId
3224 std::string dllInfo = "`." + contextId_ + "_DLLInfo`";
3225 rOfs << dllInfo << " <- dyn.load('" << dynlibPath() << "')"
3226 << std::endl << std::endl;
3227
3228 // Generate R functions
3229 generateR(rOfs, sourceAttributes, dllInfo);
3230
3231 // remove the DLLInfo
3232 rOfs << std::endl << "rm(" << dllInfo << ")"
3233 << std::endl;
3234
3235 rOfs.close();
3236
3237 // discover exported functions and dependencies
3238 exportedFunctions_.clear();
3239 depends_.clear();
3240 plugins_.clear();
3241 for (SourceFileAttributesParser::const_iterator
3242 it = sourceAttributes.begin(); it != sourceAttributes.end(); ++it) {
3243
3244 if (it->name() == kExportAttribute && !it->function().empty())
3245 exportedFunctions_.push_back(it->exportedName());
3246
3247 else if (it->name() == kDependsAttribute) {
3248 for (size_t i = 0; i<it->params().size(); ++i) // #nocov
3249 depends_.push_back(it->params()[i].name()); // #nocov
3250 }
3251
3252 else if (it->name() == kPluginsAttribute) {
3253 for (size_t i = 0; i<it->params().size(); ++i)
3254 plugins_.push_back(it->params()[i].name());
3255 }
3256 }
3257
3258 // capture modules
3259 modules_ = sourceAttributes.modules();
3260
3261 // capture embededded R
3262 embeddedR_ = sourceAttributes.embeddedR();
3263
3264 // capture source dependencies
3265 sourceDependencies_ = sourceAttributes.sourceDependencies();
3266 }
3267
contextId() const3268 const std::string& contextId() const {
3269 return contextId_;
3270 }
3271
cppSourcePath() const3272 const std::string& cppSourcePath() const {
3273 return cppSourcePath_;
3274 }
3275
cppDependencySourcePaths()3276 const std::vector<std::string> cppDependencySourcePaths() {
3277 std::vector<std::string> dependencies;
3278 for (size_t i = 0; i<sourceDependencies_.size(); ++i) {
3279 FileInfo dep = sourceDependencies_[i];
3280 if (dep.extension() == ".cc" || dep.extension() == ".cpp") {
3281 dependencies.push_back(dep.path()); // #nocov
3282 }
3283 }
3284 return dependencies;
3285 }
3286
buildDirectory() const3287 std::string buildDirectory() const {
3288 return buildDirectory_;
3289 }
3290
generatedCpp() const3291 std::string generatedCpp() const {
3292 return generatedCpp_;
3293 }
3294
cppSourceFilename() const3295 std::string cppSourceFilename() const {
3296 return cppSourceFilename_;
3297 }
3298
rSourceFilename() const3299 std::string rSourceFilename() const {
3300 return cppSourceFilename() + ".R";
3301 }
3302
dynlibFilename() const3303 std::string dynlibFilename() const {
3304 return dynlibFilename_;
3305 }
3306
dynlibPath() const3307 std::string dynlibPath() const {
3308 return buildDirectory_ + fileSep_ + dynlibFilename();
3309 }
3310
previousDynlibPath() const3311 std::string previousDynlibPath() const {
3312 if (!previousDynlibFilename_.empty())
3313 return buildDirectory_ + fileSep_ + previousDynlibFilename_; // #nocov
3314 else
3315 return std::string();
3316 }
3317
exportedFunctions() const3318 const std::vector<std::string>& exportedFunctions() const {
3319 return exportedFunctions_;
3320 }
3321
modules() const3322 const std::vector<std::string>& modules() const {
3323 return modules_;
3324 }
3325
depends() const3326 const std::vector<std::string>& depends() const { return depends_; };
3327
plugins() const3328 const std::vector<std::string>& plugins() const { return plugins_; };
3329
embeddedR() const3330 const std::vector<std::string>& embeddedR() const { return embeddedR_; }
3331
3332 private:
3333
generatedCppSourcePath() const3334 std::string generatedCppSourcePath() const {
3335 return buildDirectory_ + fileSep_ + cppSourceFilename();
3336 }
3337
generatedRSourcePath() const3338 std::string generatedRSourcePath() const {
3339 return buildDirectory_ + fileSep_ + rSourceFilename();
3340 }
3341
generateR(std::ostream & ostr,const SourceFileAttributes & attributes,const std::string & dllInfo) const3342 void generateR(std::ostream& ostr,
3343 const SourceFileAttributes& attributes,
3344 const std::string& dllInfo) const
3345 {
3346 // process each attribute
3347 for(std::vector<Attribute>::const_iterator
3348 it = attributes.begin(); it != attributes.end(); ++it) {
3349
3350 // alias the attribute and function (bail if not export)
3351 const Attribute& attribute = *it;
3352 if (!attribute.isExportedFunction())
3353 continue;
3354 const Function& function = attribute.function();
3355
3356 // export the function
3357 ostr << attribute.exportedName()
3358 << " <- Rcpp:::sourceCppFunction("
3359 << "function(" << generateRArgList(function) << ") {}, "
3360 << (function.type().isVoid() ? "TRUE" : "FALSE") << ", "
3361 << dllInfo << ", "
3362 << "'" << contextId_ + "_" + function.name()
3363 << "')" << std::endl;
3364 }
3365
3366 // modules
3367 std::vector<std::string> modules = attributes.modules();
3368 if (modules.size() > 0)
3369 {
3370 // modules require definition of C++Object to be loaded
3371 ostr << "library(Rcpp)" << std::endl;
3372
3373 // load each module
3374 for (std::vector<std::string>::const_iterator
3375 it = modules.begin(); it != modules.end(); ++it)
3376 {
3377 ostr << " populate( Rcpp::Module(\"" << *it << "\","
3378 << dllInfo << "), environment() ) " << std::endl;
3379 }
3380 }
3381
3382 }
3383
uniqueToken(const std::string & cacheDir)3384 std::string uniqueToken(const std::string& cacheDir) {
3385 Rcpp::Environment rcppEnv = Rcpp::Environment::namespace_env("Rcpp");
3386 Rcpp::Function uniqueTokenFunc = rcppEnv[".sourceCppDynlibUniqueToken"];
3387 return Rcpp::as<std::string>(uniqueTokenFunc(cacheDir));
3388 }
3389
3390 private:
3391 std::string cppSourcePath_;
3392 std::string generatedCpp_;
3393 std::string cppSourceFilename_;
3394 std::string contextId_;
3395 std::string buildDirectory_;
3396 std::string fileSep_;
3397 std::string dynlibFilename_;
3398 std::string previousDynlibFilename_;
3399 std::string dynlibExt_;
3400 std::vector<std::string> exportedFunctions_;
3401 std::vector<std::string> modules_;
3402 std::vector<std::string> depends_;
3403 std::vector<std::string> plugins_;
3404 std::vector<std::string> embeddedR_;
3405 std::vector<FileInfo> sourceDependencies_;
3406 };
3407
3408 // Dynlib cache that allows lookup by either file path or code contents
3409
dynlibCacheInsert(const std::string & cacheDir,const std::string & file,const std::string & code,const SourceCppDynlib & dynlib)3410 void dynlibCacheInsert(const std::string& cacheDir,
3411 const std::string& file,
3412 const std::string& code,
3413 const SourceCppDynlib& dynlib)
3414 {
3415 Rcpp::Environment rcppEnv = Rcpp::Environment::namespace_env("Rcpp");
3416 Rcpp::Function dynlibInsertFunc = rcppEnv[".sourceCppDynlibInsert"];
3417 dynlibInsertFunc(cacheDir, file, code, dynlib.toList());
3418 }
3419
dynlibCacheInsertFile(const std::string & cacheDir,const std::string & file,const SourceCppDynlib & dynlib)3420 void dynlibCacheInsertFile(const std::string& cacheDir,
3421 const std::string& file,
3422 const SourceCppDynlib& dynlib)
3423 {
3424 dynlibCacheInsert(cacheDir, file, "", dynlib);
3425 }
3426
dynlibCacheInsertCode(const std::string & cacheDir,const std::string & code,const SourceCppDynlib & dynlib)3427 void dynlibCacheInsertCode(const std::string& cacheDir,
3428 const std::string& code,
3429 const SourceCppDynlib& dynlib)
3430 {
3431 dynlibCacheInsert(cacheDir, "", code, dynlib);
3432 }
3433
dynlibCacheLookup(const std::string & cacheDir,const std::string & file,const std::string & code)3434 SourceCppDynlib dynlibCacheLookup(const std::string& cacheDir,
3435 const std::string& file,
3436 const std::string& code)
3437 {
3438 Rcpp::Environment rcppEnv = Rcpp::Environment::namespace_env("Rcpp");
3439 Rcpp::Function dynlibLookupFunc = rcppEnv[".sourceCppDynlibLookup"];
3440 Rcpp::List dynlibList = dynlibLookupFunc(cacheDir, file, code);
3441 if (dynlibList.length() > 0)
3442 return SourceCppDynlib(dynlibList);
3443 else
3444 return SourceCppDynlib();
3445 }
3446
dynlibCacheLookupByFile(const std::string & cacheDir,const std::string & file)3447 SourceCppDynlib dynlibCacheLookupByFile(const std::string& cacheDir,
3448 const std::string& file)
3449 {
3450 return dynlibCacheLookup(cacheDir, file, "");
3451 }
3452
dynlibCacheLookupByCode(const std::string & cacheDir,const std::string & code)3453 SourceCppDynlib dynlibCacheLookupByCode(const std::string& cacheDir,
3454 const std::string& code)
3455 {
3456 return dynlibCacheLookup(cacheDir, "", code);
3457 }
3458
3459 } // anonymous namespace
3460
3461 // Create temporary build directory, generate code as necessary, and return
3462 // the context required for the sourceCpp function to complete it's work
sourceCppContext(SEXP sFile,SEXP sCode,SEXP sRebuild,SEXP sCacheDir,SEXP sPlatform)3463 RcppExport SEXP sourceCppContext(SEXP sFile, SEXP sCode,
3464 SEXP sRebuild, SEXP sCacheDir, SEXP sPlatform) {
3465 BEGIN_RCPP
3466 // parameters
3467 std::string file = Rcpp::as<std::string>(sFile);
3468 std::string code = sCode != R_NilValue ? Rcpp::as<std::string>(sCode) : "";
3469 bool rebuild = Rcpp::as<bool>(sRebuild);
3470 std::string cacheDir = Rcpp::as<std::string>(sCacheDir);
3471 Rcpp::List platform = Rcpp::as<Rcpp::List>(sPlatform);
3472
3473 // get dynlib (using cache if possible)
3474 SourceCppDynlib dynlib = !code.empty() ? dynlibCacheLookupByCode(cacheDir, code)
3475 : dynlibCacheLookupByFile(cacheDir, file);
3476
3477 // check dynlib build state
3478 bool buildRequired = false;
3479
3480 // if there is no dynlib in the cache then create a new one
3481 if (dynlib.isEmpty()) {
3482 buildRequired = true;
3483 dynlib = SourceCppDynlib(cacheDir, file, platform);
3484 }
3485
3486 // if the cached dynlib is dirty then regenerate the source
3487 else if (rebuild || dynlib.isSourceDirty()) {
3488 buildRequired = true; // #nocov
3489 dynlib.regenerateSource(cacheDir); // #nocov
3490 }
3491
3492 // if the dynlib hasn't yet been built then note that
3493 else if (!dynlib.isBuilt()) {
3494 buildRequired = true; // #nocov
3495 }
3496
3497 // save the dynlib to the cache
3498 if (!code.empty())
3499 dynlibCacheInsertCode(cacheDir, code, dynlib);
3500 else
3501 dynlibCacheInsertFile(cacheDir, file, dynlib);
3502
3503 // return context as a list
3504 using namespace Rcpp;
3505 return List::create(
3506 _["contextId"] = dynlib.contextId(),
3507 _["cppSourcePath"] = dynlib.cppSourcePath(),
3508 _["cppDependencySourcePaths"] = dynlib.cppDependencySourcePaths(),
3509 _["buildRequired"] = buildRequired,
3510 _["buildDirectory"] = dynlib.buildDirectory(),
3511 _["generatedCpp"] = dynlib.generatedCpp(),
3512 _["exportedFunctions"] = dynlib.exportedFunctions(),
3513 _["modules"] = dynlib.modules(),
3514 _["cppSourceFilename"] = dynlib.cppSourceFilename(),
3515 _["rSourceFilename"] = dynlib.rSourceFilename(),
3516 _["dynlibFilename"] = dynlib.dynlibFilename(),
3517 _["dynlibPath"] = dynlib.dynlibPath(),
3518 _["previousDynlibPath"] = dynlib.previousDynlibPath(),
3519 _["depends"] = dynlib.depends(),
3520 _["plugins"] = dynlib.plugins(),
3521 _["embeddedR"] = dynlib.embeddedR());
3522 END_RCPP
3523 }
3524
3525 // Compile the attributes within the specified package directory into
3526 // RcppExports.cpp and RcppExports.R
compileAttributes(SEXP sPackageDir,SEXP sPackageName,SEXP sDepends,SEXP sRegistration,SEXP sCppFiles,SEXP sCppFileBasenames,SEXP sIncludes,SEXP sVerbose,SEXP sPlatform)3527 RcppExport SEXP compileAttributes(SEXP sPackageDir,
3528 SEXP sPackageName,
3529 SEXP sDepends,
3530 SEXP sRegistration,
3531 SEXP sCppFiles,
3532 SEXP sCppFileBasenames,
3533 SEXP sIncludes,
3534 SEXP sVerbose,
3535 SEXP sPlatform) {
3536 BEGIN_RCPP
3537 // arguments
3538 std::string packageDir = Rcpp::as<std::string>(sPackageDir);
3539 std::string packageName = Rcpp::as<std::string>(sPackageName);
3540
3541 Rcpp::CharacterVector vDepends = Rcpp::as<Rcpp::CharacterVector>(sDepends);
3542 std::set<std::string> depends;
3543 for (Rcpp::CharacterVector::iterator
3544 it = vDepends.begin(); it != vDepends.end(); ++it) {
3545 depends.insert(std::string(*it));
3546 }
3547
3548 bool registration = Rcpp::as<bool>(sRegistration);
3549
3550 std::vector<std::string> cppFiles =
3551 Rcpp::as<std::vector<std::string> >(sCppFiles);
3552 std::vector<std::string> cppFileBasenames =
3553 Rcpp::as<std::vector<std::string> >(sCppFileBasenames);
3554 std::vector<std::string> includes =
3555 Rcpp::as<std::vector<std::string> >(sIncludes);
3556 bool verbose = Rcpp::as<bool>(sVerbose);
3557 Rcpp::List platform = Rcpp::as<Rcpp::List>(sPlatform);
3558 std::string fileSep = Rcpp::as<std::string>(platform["file.sep"]);
3559
3560 // initialize generators
3561 ExportsGenerators generators;
3562 generators.add(new CppExportsGenerator(packageDir, packageName, fileSep));
3563 generators.add(new RExportsGenerator(packageDir, packageName, registration, fileSep));
3564
3565 // catch file exists exception if the include file already exists
3566 // and we are unable to overwrite it
3567 try {
3568 generators.add(new CppExportsIncludeGenerator(packageDir,
3569 packageName,
3570 fileSep));
3571 }
3572 catch(const Rcpp::file_exists& e) {
3573 std::string msg =
3574 "The header file '" + e.filePath() + "' already exists so "
3575 "cannot be overwritten by Rcpp::interfaces";
3576 throw Rcpp::exception(msg.c_str(), __FILE__, __LINE__);
3577 }
3578
3579 // catch file exists exception for package include (because if it
3580 // already exists we simply leave it alone)
3581 try {
3582 generators.add(new CppPackageIncludeGenerator(packageDir,
3583 packageName,
3584 fileSep));
3585 }
3586 catch(const Rcpp::file_exists& e) {}
3587
3588 // write begin
3589 generators.writeBegin();
3590
3591 // Parse attributes from each file and generate code as required.
3592 bool hasPackageInit = false;
3593 bool haveAttributes = false;
3594 std::set<std::string> dependsAttribs;
3595 for (std::size_t i=0; i<cppFiles.size(); i++) {
3596
3597 // don't process RcppExports.cpp
3598 std::string cppFile = cppFiles[i];
3599 if (endsWith(cppFile, "RcppExports.cpp"))
3600 continue; // #nocov
3601
3602 // parse file
3603 SourceFileAttributesParser attributes(cppFile, packageName, false);
3604
3605 // note if we found a package init function
3606 if (!hasPackageInit && attributes.hasPackageInit())
3607 hasPackageInit = true; // #nocov
3608
3609 // continue if no generator output
3610 if (!attributes.hasGeneratorOutput())
3611 continue; // #nocov
3612
3613 // confirm we have attributes
3614 haveAttributes = true;
3615
3616 // write functions
3617 generators.writeFunctions(attributes, verbose);
3618
3619 // track depends
3620 for (SourceFileAttributesParser::const_iterator
3621 it = attributes.begin(); it != attributes.end(); ++it) {
3622 if (it->name() == kDependsAttribute) {
3623 for (size_t i = 0; i<it->params().size(); ++i) // #nocov
3624 dependsAttribs.insert(it->params()[i].name()); // #nocov
3625 }
3626 }
3627 }
3628
3629 // write end
3630 generators.writeEnd(hasPackageInit);
3631
3632 // commit or remove
3633 std::vector<std::string> updated;
3634 if (haveAttributes)
3635 updated = generators.commit(includes);
3636 else
3637 updated = generators.remove(); // #nocov
3638
3639 // print warning if there are depends attributes that don't have
3640 // corresponding entries in the DESCRIPTION file
3641 std::vector<std::string> diff;
3642 std::set_difference(dependsAttribs.begin(), dependsAttribs.end(),
3643 depends.begin(), depends.end(),
3644 std::back_inserter(diff));
3645 if (!diff.empty()) {
3646 std::string msg = // #nocov start
3647 "The following packages are referenced using Rcpp::depends "
3648 "attributes however are not listed in the Depends, Imports or "
3649 "LinkingTo fields of the package DESCRIPTION file: ";
3650 for (size_t i=0; i<diff.size(); i++) {
3651 msg += diff[i];
3652 if (i != (diff.size()-1))
3653 msg += ", ";
3654 }
3655 showWarning(msg);
3656 }
3657
3658 // verbose output
3659 if (verbose) {
3660 for (size_t i=0; i<updated.size(); i++)
3661 Rcpp::Rcout << updated[i] << " updated." << std::endl; // #nocov end
3662 }
3663
3664 // return files updated
3665 return Rcpp::wrap<std::vector<std::string> >(updated);
3666 END_RCPP
3667 }
3668