/* * Copyright 2016, 2017 Tobias Grosser. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * THIS SOFTWARE IS PROVIDED BY TOBIAS GROSSER ''AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL TOBIAS GROSSER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * The views and conclusions contained in the software and documentation * are those of the authors and should not be interpreted as * representing official policies, either expressed or implied, of * Tobias Grosser. */ #include #include #include #include #include #include #include #include #include "plain_cpp.h" #include "isl_config.h" /* Print string formatted according to "fmt" to ostream "os". * * This osprintf method allows us to use printf style formatting constructs when * writing to an ostream. */ static void osprintf(ostream &os, const char *format, va_list arguments) { va_list copy; char *string_pointer; size_t size; va_copy(copy, arguments); size = vsnprintf(NULL, 0, format, copy); string_pointer = new char[size + 1]; va_end(copy); vsnprintf(string_pointer, size + 1, format, arguments); os << string_pointer; delete[] string_pointer; } /* Print string formatted according to "fmt" to ostream "os". * * This osprintf method allows us to use printf style formatting constructs when * writing to an ostream. */ static void osprintf(ostream &os, const char *format, ...) { va_list arguments; va_start(arguments, format); osprintf(os, format, arguments); va_end(arguments); } /* Print string formatted according to "fmt" to ostream "os" * with the given indentation. * * This osprintf method allows us to use printf style formatting constructs when * writing to an ostream. */ static void osprintf(ostream &os, int indent, const char *format, ...) { va_list arguments; osprintf(os, "%*s", indent, " "); va_start(arguments, format); osprintf(os, format, arguments); va_end(arguments); } /* Convert "l" to a string. */ static std::string to_string(long l) { std::ostringstream strm; strm << l; return strm.str(); } /* Construct a generator for plain C++ bindings. * * "checked" is set if C++ bindings should be generated * that rely on the user to check for error conditions. */ plain_cpp_generator::plain_cpp_generator(SourceManager &SM, set &exported_types, set exported_functions, set functions, bool checked) : cpp_generator(SM, exported_types, exported_functions, functions), checked(checked) { } /* Generate a cpp interface based on the extracted types and functions. * * Print first a set of forward declarations for all isl wrapper * classes, then the declarations of the classes, and at the end all * implementations. * * If checked C++ bindings are being generated, * then wrap them in a namespace to avoid conflicts * with the default C++ bindings (with automatic checks using exceptions). */ void plain_cpp_generator::generate() { ostream &os = cout; osprintf(os, "\n"); osprintf(os, "namespace isl {\n\n"); if (checked) osprintf(os, "namespace checked {\n\n"); print_forward_declarations(os); osprintf(os, "\n"); print_declarations(os); osprintf(os, "\n"); print_implementations(os); if (checked) osprintf(os, "} // namespace checked\n"); osprintf(os, "} // namespace isl\n"); } /* Print forward declarations for all classes to "os". */ void plain_cpp_generator::print_forward_declarations(ostream &os) { map::iterator ci; osprintf(os, "// forward declarations\n"); for (ci = classes.begin(); ci != classes.end(); ++ci) print_class_forward_decl(os, ci->second); } /* Print all declarations to "os". */ void plain_cpp_generator::print_declarations(ostream &os) { map::iterator ci; bool first = true; for (ci = classes.begin(); ci != classes.end(); ++ci) { if (first) first = false; else osprintf(os, "\n"); print_class(os, ci->second); } } /* Print all implementations to "os". */ void plain_cpp_generator::print_implementations(ostream &os) { map::iterator ci; bool first = true; for (ci = classes.begin(); ci != classes.end(); ++ci) { if (first) first = false; else osprintf(os, "\n"); print_class_impl(os, ci->second); } } /* If the printed class is a subclass that is based on a type function, * then introduce a "type" field that holds the value of the type * corresponding to the subclass and make the fields of the class * accessible to the "isa" and "as" methods of the (immediate) superclass. * In particular, "isa" needs access to the type field itself, * while "as" needs access to the private constructor. * In case of the "isa" method, all instances are made friends * to avoid access right confusion. */ void plain_cpp_generator::decl_printer::print_subclass_type() { std::string super; const char *cppname = cppstring.c_str(); const char *supername; if (!clazz.is_type_subclass()) return; super = type2cpp(clazz.superclass_name); supername = super.c_str(); osprintf(os, " template \n"); osprintf(os, " friend %s %s::isa() const;\n", generator.isl_bool2cpp().c_str(), supername); osprintf(os, " friend %s %s::as<%s>() const;\n", cppname, supername, cppname); osprintf(os, " static const auto type = %s;\n", clazz.subclass_name.c_str()); } /* Print declarations for class "clazz" to "os". * * If "clazz" is a subclass based on a type function, * then it is made to inherit from the (immediate) superclass and * a "type" attribute is added for use in the "as" and "isa" * methods of the superclass. * * Conversely, if "clazz" is a superclass with a type function, * then declare those "as" and "isa" methods. * * The pointer to the isl object is only added for classes that * are not subclasses, since subclasses refer to the same isl object. */ void plain_cpp_generator::print_class(ostream &os, const isl_class &clazz) { decl_printer printer(os, clazz, *this); const char *name = clazz.name.c_str(); const char *cppname = printer.cppstring.c_str(); osprintf(os, "// declarations for isl::%s\n", cppname); printer.print_class_factory(); osprintf(os, "\n"); osprintf(os, "class %s ", cppname); if (clazz.is_type_subclass()) osprintf(os, ": public %s ", type2cpp(clazz.superclass_name).c_str()); osprintf(os, "{\n"); printer.print_subclass_type(); printer.print_class_factory(" friend "); osprintf(os, "\n"); osprintf(os, "protected:\n"); if (!clazz.is_type_subclass()) { osprintf(os, " %s *ptr = nullptr;\n", name); osprintf(os, "\n"); } printer.print_protected_constructors(); osprintf(os, "\n"); osprintf(os, "public:\n"); printer.print_public_methods(); osprintf(os, "};\n"); } /* Print forward declaration of class "clazz" to "os". */ void plain_cpp_generator::print_class_forward_decl(ostream &os, const isl_class &clazz) { std::string cppstring = type2cpp(clazz); const char *cppname = cppstring.c_str(); osprintf(os, "class %s;\n", cppname); } /* Print global factory functions. * * Each class has two global factory functions: * * set manage(__isl_take isl_set *ptr); * set manage_copy(__isl_keep isl_set *ptr); * * A user can construct isl C++ objects from a raw pointer and indicate whether * they intend to take the ownership of the object or not through these global * factory functions. This ensures isl object creation is very explicit and * pointers are not converted by accident. Thanks to overloading, manage() and * manage_copy() can be called on any isl raw pointer and the corresponding * object is automatically created, without the user having to choose the right * isl object type. * * For a subclass based on a type function, no factory functions * are introduced because they share the C object type with * the superclass. */ void plain_cpp_generator::decl_printer::print_class_factory( const std::string &prefix) { const char *name = clazz.name.c_str(); const char *cppname = cppstring.c_str(); if (clazz.is_type_subclass()) return; os << prefix; osprintf(os, "inline %s manage(__isl_take %s *ptr);\n", cppname, name); os << prefix; osprintf(os, "inline %s manage_copy(__isl_keep %s *ptr);\n", cppname, name); } /* Print declarations of protected constructors. * * Each class has currently one protected constructor: * * 1) Constructor from a plain isl_* C pointer * * Example: * * set(__isl_take isl_set *ptr); * * The raw pointer constructor is kept protected. Object creation is only * possible through manage() or manage_copy(). */ void plain_cpp_generator::decl_printer::print_protected_constructors() { const char *name = clazz.name.c_str(); const char *cppname = cppstring.c_str(); osprintf(os, " inline explicit %s(__isl_take %s *ptr);\n", cppname, name); } /* Print declarations of public constructors. * * Each class currently has two public constructors: * * 1) A default constructor * 2) A copy constructor * * Example: * * set(); * set(const set &set); */ void plain_cpp_generator::decl_printer::print_public_constructors() { const char *cppname = cppstring.c_str(); osprintf(os, " inline /* implicit */ %s();\n", cppname); osprintf(os, " inline /* implicit */ %s(const %s &obj);\n", cppname, cppname); } /* Print declarations for "method". */ void plain_cpp_generator::decl_printer::print_method( const ConversionMethod &method) { print_full_method_header(method); } /* Print declarations for "method". */ void plain_cpp_generator::decl_printer::print_method(const Method &method) { print_full_method_header(method); } /* Print a declaration for a constructor for the "id" class * that takes a user object. */ void plain_cpp_generator::decl_printer::print_id_constructor_user() { print_id_constructor_user_header(); } /* Print a declaration for an "id" method * for retrieving the user object associated to the identifier. * If "optional" is set, the method returns a std::optional user object. */ void plain_cpp_generator::decl_printer::print_id_user(bool optional) { print_id_user_header(optional); } /* Print declarations of copy assignment operator. * * Each class has one assignment operator. * * isl:set &set::operator=(set obj) * */ void plain_cpp_generator::decl_printer::print_copy_assignment() { const char *cppname = cppstring.c_str(); osprintf(os, " inline %s &operator=(%s obj);\n", cppname, cppname); } /* Print declaration of destructor. * * No explicit destructor is needed for type based subclasses. */ void plain_cpp_generator::decl_printer::print_destructor() { const char *cppname = cppstring.c_str(); if (clazz.is_type_subclass()) return; osprintf(os, " inline ~%s();\n", cppname); } /* Print declaration of pointer functions. * Since type based subclasses share the pointer with their superclass, * they can also reuse these functions from the superclass. * * To obtain a raw pointer three functions are provided: * * 1) __isl_give isl_set *copy() * * Returns a pointer to a _copy_ of the internal object * * 2) __isl_keep isl_set *get() * * Returns a pointer to the internal object * * 3) __isl_give isl_set *release() * * Returns a pointer to the internal object and resets the * internal pointer to nullptr. * * We also provide functionality to explicitly check if a pointer is * currently managed by this object. * * 4) bool is_null() * * Check if the current object is a null pointer. * * The functions get() and release() model the value_ptr proposed in * http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3339.pdf. * The copy() function is an extension to allow the user to explicitly * copy the underlying object. * * Also generate a declaration to delete copy() for r-values, for * r-values release() should be used to avoid unnecessary copies. */ void plain_cpp_generator::decl_printer::print_ptr() { const char *name = clazz.name.c_str(); if (clazz.is_type_subclass()) return; osprintf(os, " inline __isl_give %s *copy() const &;\n", name); osprintf(os, " inline __isl_give %s *copy() && = delete;\n", name); osprintf(os, " inline __isl_keep %s *get() const;\n", name); osprintf(os, " inline __isl_give %s *release();\n", name); osprintf(os, " inline bool is_null() const;\n"); } /* Print a template declaration with given indentation * for the "isa_type" method that ensures it is only enabled * when called with a template argument * that represents a type that is equal to that * of the return type of the type function of "super". * In particular, "isa_type" gets called from "isa" * with as template argument the type of the "type" field * of the subclass. * The check ensures that this subclass is in fact a direct subclass * of "super". */ void plain_cpp_generator::decl_printer::print_isa_type_template(int indent, const isl_class &super) { osprintf(os, indent, "template getNameAsString().c_str()); osprintf(os, indent, " const T>::value>::type>\n"); } /* Print declarations for the "as" and "isa" methods, if the printed class * is a superclass with a type function. * * "isa" checks whether an object is of a given subclass type. * "isa_type" does the same, but gets passed the value of the type field * of the subclass as a function argument and the type of this field * as a template argument. * "as" tries to cast an object to a given subclass type, returning * an invalid object if the object is not of the given type. */ void plain_cpp_generator::decl_printer::print_downcast() { if (!clazz.fn_type) return; osprintf(os, "private:\n"); print_isa_type_template(2, clazz); osprintf(os, " inline %s isa_type(T subtype) const;\n", generator.isl_bool2cpp().c_str()); osprintf(os, "public:\n"); osprintf(os, " template inline %s isa() const;\n", generator.isl_bool2cpp().c_str()); osprintf(os, " template inline T as() const;\n"); } /* Print the declaration of the ctx method. */ void plain_cpp_generator::decl_printer::print_ctx() { std::string ns = generator.isl_namespace(); osprintf(os, " inline %sctx ctx() const;\n", ns.c_str()); } /* Print a separator between groups of method declarations. */ void plain_cpp_generator::decl_printer::print_method_separator() { os << "\n"; } /* Add a space to the return type "type" if needed, * i.e., if it is not the type of a pointer. */ static string add_space_to_return_type(const string &type) { if (type[type.size() - 1] == '*') return type; return type + " "; } /* Print the prototype of the static inline method that is used * as the C callback set by "method". */ void plain_cpp_generator::plain_printer::print_persistent_callback_prototype( FunctionDecl *method) { string callback_name, rettype, c_args; ParmVarDecl *param = persistent_callback_arg(method); const FunctionProtoType *callback; QualType ptype; string classname; ptype = param->getType(); callback = extract_prototype(ptype); rettype = callback->getReturnType().getAsString(); rettype = add_space_to_return_type(rettype); callback_name = clazz.persistent_callback_name(method); c_args = generator.generate_callback_args(ptype, false); if (!declarations) classname = type2cpp(clazz) + "::"; osprintf(os, "%s%s%s(%s)", rettype.c_str(), classname.c_str(), callback_name.c_str(), c_args.c_str()); } /* Print the prototype of the method for setting the callback function * set by "method". */ void plain_cpp_generator::plain_printer::print_persistent_callback_setter_prototype( FunctionDecl *method) { string classname, callback_name, cpptype; ParmVarDecl *param = persistent_callback_arg(method); if (!declarations) classname = type2cpp(clazz) + "::"; cpptype = generator.param2cpp(param->getOriginalType()); callback_name = clazz.persistent_callback_name(method); osprintf(os, "void %sset_%s_data(const %s &%s)", classname.c_str(), callback_name.c_str(), cpptype.c_str(), param->getName().str().c_str()); } /* Given a method "method" for setting a persistent callback, * print the fields that are needed for marshalling the callback. * * In particular, print * - the declaration of a data structure for storing the C++ callback function * - a shared pointer to such a data structure * - the declaration of a static inline method * for use as the C callback function * - the declaration of a private method for setting the callback function */ void plain_cpp_generator::decl_printer::print_persistent_callback_data( FunctionDecl *method) { string callback_name; ParmVarDecl *param = generator.persistent_callback_arg(method); callback_name = clazz.persistent_callback_name(method); print_callback_data_decl(param, callback_name); osprintf(os, ";\n"); osprintf(os, " std::shared_ptr<%s_data> %s_data;\n", callback_name.c_str(), callback_name.c_str()); osprintf(os, " static inline "); print_persistent_callback_prototype(method); osprintf(os, ";\n"); osprintf(os, " inline "); print_persistent_callback_setter_prototype(method); osprintf(os, ";\n"); } /* Print declarations needed for the persistent callbacks of the class. * * In particular, if there are any persistent callbacks, then * print a private method for copying callback data from * one object to another, * private data for keeping track of the persistent callbacks and * public methods for setting the persistent callbacks. */ void plain_cpp_generator::decl_printer::print_persistent_callbacks() { const char *cppname = cppstring.c_str(); if (!clazz.has_persistent_callbacks()) return; osprintf(os, "private:\n"); osprintf(os, " inline %s ©_callbacks(const %s &obj);\n", cppname, cppname); for (const auto &callback : clazz.persistent_callbacks) print_persistent_callback_data(callback); osprintf(os, "public:\n"); for (const auto &callback : clazz.persistent_callbacks) print_method(Method(clazz, callback)); } /* Print a declaration for the "get" method "fd", * using a name that includes the "get_" prefix. */ void plain_cpp_generator::decl_printer::print_get_method(FunctionDecl *fd) { string base = clazz.base_method_name(fd); print_method(Method(clazz, fd, base)); } /* Print implementations for class "clazz" to "os". */ void plain_cpp_generator::print_class_impl(ostream &os, const isl_class &clazz) { impl_printer printer(os, clazz, *this); const char *cppname = printer.cppstring.c_str(); osprintf(os, "// implementations for isl::%s", cppname); printer.print_class_factory(); printer.print_protected_constructors(); printer.print_public_methods(); printer.print_stream_insertion(); } /* Print code for throwing an exception corresponding to the last error * that occurred on "saved_ctx". * This assumes that a valid isl::ctx is available in the "saved_ctx" variable, * e.g., through a prior call to print_save_ctx. */ static void print_throw_last_error(ostream &os) { osprintf(os, " exception::throw_last_error(saved_ctx);\n"); } /* Print code with the given indentation * for throwing an exception_invalid with the given message. */ static void print_throw_invalid(ostream &os, int indent, const char *msg) { osprintf(os, indent, "exception::throw_invalid(\"%s\", __FILE__, __LINE__);\n", msg); } /* Print code for throwing an exception on NULL input. */ static void print_throw_NULL_input(ostream &os) { print_throw_invalid(os, 4, "NULL input"); } /* Print code with the given indentation * for acting on an invalid error with message "msg". * In particular, throw an exception_invalid. * In the checked C++ bindings, isl_die is called instead with the code * in "checked_code". */ void plain_cpp_generator::print_invalid(ostream &os, int indent, const char *msg, const char *checked_code) { if (checked) osprintf(os, indent, "isl_die(ctx().get(), isl_error_invalid, " "\"%s\", %s);\n", msg, checked_code); else print_throw_invalid(os, indent, msg); } /* Print an operator for inserting objects of the class * into an output stream. * * Unless checked C++ bindings are being generated, * the operator requires its argument to be non-NULL. * An exception is thrown if anything went wrong during the printing. * During this printing, isl is made not to print any error message * because the error message is included in the exception. * * If checked C++ bindings are being generated and anything went wrong, * then record this failure in the output stream. */ void plain_cpp_generator::impl_printer::print_stream_insertion() { const char *name = clazz.name.c_str(); const char *cppname = cppstring.c_str(); if (!clazz.fn_to_str) return; osprintf(os, "\n"); osprintf(os, "inline std::ostream &operator<<(std::ostream &os, "); osprintf(os, "const %s &obj)\n", cppname); osprintf(os, "{\n"); print_check_ptr_start("obj.get()"); osprintf(os, " char *str = %s_to_str(obj.get());\n", name); print_check_ptr_end("str"); if (generator.checked) { osprintf(os, " if (!str) {\n"); osprintf(os, " os.setstate(std::ios_base::badbit);\n"); osprintf(os, " return os;\n"); osprintf(os, " }\n"); } osprintf(os, " os << str;\n"); osprintf(os, " free(str);\n"); osprintf(os, " return os;\n"); osprintf(os, "}\n"); } /* Print code that checks that "ptr" is not NULL at input. * * Omit the check if checked C++ bindings are being generated. */ void plain_cpp_generator::impl_printer::print_check_ptr(const char *ptr) { if (generator.checked) return; osprintf(os, " if (!%s)\n", ptr); print_throw_NULL_input(os); } /* Print code that checks that "ptr" is not NULL at input and * that saves a copy of the isl_ctx of "ptr" for a later check. * * Omit the check if checked C++ bindings are being generated. */ void plain_cpp_generator::impl_printer::print_check_ptr_start(const char *ptr) { if (generator.checked) return; print_check_ptr(ptr); print_save_ctx(clazz.name + "_get_ctx(" + ptr + ")"); print_on_error_continue(); } /* Print code that checks that "ptr" is not NULL at the end. * A copy of the isl_ctx is expected to have been saved by * code generated by print_check_ptr_start. * * Omit the check if checked C++ bindings are being generated. */ void plain_cpp_generator::impl_printer::print_check_ptr_end(const char *ptr) { if (generator.checked) return; osprintf(os, " if (!%s)\n", ptr); print_throw_last_error(os); } /* Print implementation of global factory functions. * * Each class has two global factory functions: * * set manage(__isl_take isl_set *ptr); * set manage_copy(__isl_keep isl_set *ptr); * * Unless checked C++ bindings are being generated, * both functions require the argument to be non-NULL. * An exception is thrown if anything went wrong during the copying * in manage_copy. * During the copying, isl is made not to print any error message * because the error message is included in the exception. * * For a subclass based on a type function, no factory functions * are introduced because they share the C object type with * the superclass. */ void plain_cpp_generator::impl_printer::print_class_factory() { const char *name = clazz.name.c_str(); const char *cppname = cppstring.c_str(); if (clazz.is_type_subclass()) return; osprintf(os, "\n"); osprintf(os, "%s manage(__isl_take %s *ptr) {\n", cppname, name); print_check_ptr("ptr"); osprintf(os, " return %s(ptr);\n", cppname); osprintf(os, "}\n"); osprintf(os, "%s manage_copy(__isl_keep %s *ptr) {\n", cppname, name); print_check_ptr_start("ptr"); osprintf(os, " ptr = %s_copy(ptr);\n", name); print_check_ptr_end("ptr"); osprintf(os, " return %s(ptr);\n", cppname); osprintf(os, "}\n"); } /* Print implementations of protected constructors. * * The pointer to the isl object is either initialized directly or * through the (immediate) superclass. */ void plain_cpp_generator::impl_printer::print_protected_constructors() { const char *name = clazz.name.c_str(); const char *cppname = cppstring.c_str(); bool subclass = clazz.is_type_subclass(); osprintf(os, "\n"); osprintf(os, "%s::%s(__isl_take %s *ptr)\n", cppname, cppname, name); if (subclass) osprintf(os, " : %s(ptr) {}\n", type2cpp(clazz.superclass_name).c_str()); else osprintf(os, " : ptr(ptr) {}\n"); } /* Print implementations of public constructors. * * The pointer to the isl object is either initialized directly or * through the (immediate) superclass. * * If the class has any persistent callbacks, then copy them * from the original object in the copy constructor. * If the class is a subclass, then the persistent callbacks * are assumed to be copied by the copy constructor of the superclass. * * Throw an exception from the copy constructor if anything went wrong * during the copying or if the input is NULL, if any copying is performed. * During the copying, isl is made not to print any error message * because the error message is included in the exception. * No exceptions are thrown if checked C++ bindings * are being generated, */ void plain_cpp_generator::impl_printer::print_public_constructors() { std::string super; const char *cppname = cppstring.c_str(); bool subclass = clazz.is_type_subclass(); osprintf(os, "\n"); if (subclass) super = type2cpp(clazz.superclass_name); osprintf(os, "%s::%s()\n", cppname, cppname); if (subclass) osprintf(os, " : %s() {}\n\n", super.c_str()); else osprintf(os, " : ptr(nullptr) {}\n\n"); osprintf(os, "%s::%s(const %s &obj)\n", cppname, cppname, cppname); if (subclass) osprintf(os, " : %s(obj)\n", super.c_str()); else osprintf(os, " : ptr(nullptr)\n"); osprintf(os, "{\n"); if (!subclass) { print_check_ptr_start("obj.ptr"); osprintf(os, " ptr = obj.copy();\n"); if (clazz.has_persistent_callbacks()) osprintf(os, " copy_callbacks(obj);\n"); print_check_ptr_end("ptr"); } osprintf(os, "}\n"); } /* Print definition for "method", * without any automatic type conversions. * * This method distinguishes three kinds of methods: member methods, static * methods, and constructors. * * Member methods and static methods return a newly managed * isl C++ object. * * Constructors create a new object from a given set of input parameters. They * do not return a value, but instead update the pointer stored inside the * newly created object. * * Unless checked C++ bindings are being generated, * the inputs of the method are first checked for being valid isl objects and * a copy of the associated isl::ctx is saved (if needed). * If any failure occurs, either during the check for the inputs or * during the isl function call, an exception is thrown. * During the function call, isl is made not to print any error message * because the error message is included in the exception. */ void plain_cpp_generator::impl_printer::print_method(const Method &method) { string methodname = method.fd->getName().str(); int num_params = method.c_num_params(); osprintf(os, "\n"); print_full_method_header(method); osprintf(os, "{\n"); print_argument_validity_check(method); print_save_ctx(method); print_on_error_continue(); for (const auto &callback : method.callbacks) print_callback_local(callback); osprintf(os, " auto res = %s", methodname.c_str()); method.print_fd_arg_list(os, 0, num_params, [&] (int i, int arg) { method.print_param_use(os, i); }); osprintf(os, ";\n"); print_exceptional_execution_check(method); if (method.kind == Method::Kind::constructor) { osprintf(os, " ptr = res;\n"); } else { print_method_return(method); } osprintf(os, "}\n"); } /* Convert argument of type "src" to "dst", with a name specified by "dst". * * If "src" is the same as "dst", then no argument conversion is needed. * * Otherwise, call the conversion function * with as arguments the isl_ctx of the object and the argument name, * or simply the argument name if the source type is an isl type. * This means this isl_ctx should be available. */ void plain_cpp_generator::impl_printer::print_arg_conversion(ParmVarDecl *dst, ParmVarDecl *src) { std::string name = dst->getName().str(); QualType type = dst->getOriginalType(); string cpptype = generator.param2cpp(type); if (dst == src) os << name; else if (is_isl_type(src->getOriginalType())) os << cpptype << "(" << name << ")"; else os << cpptype << "(ctx(), " << name << ")"; } /* Print a definition for "method", * where "this" or at least one of the argument types needs to be converted. * * "method" is assumed to be a member method. * * The generated method performs the required conversion(s) and * calls the method generated without conversions. * * Perform a conversion from the argument in the method declaration * (as specified by Method::get_param) to the argument of the C function, * if needed. * Such a conversion may require the isl_ctx to be available. * In order to be able to use this isl_ctx, the current object needs * to valid. The validity of other arguments is checked * by the called method. */ void plain_cpp_generator::impl_printer::print_method( const ConversionMethod &method) { if (method.kind != Method::Kind::member_method) die("Automatic conversion currently only supported " "for object methods"); osprintf(os, "\n"); print_full_method_header(method); osprintf(os, "{\n"); print_check_ptr("ptr"); osprintf(os, " return "); method.print_call(os, generator.isl_namespace()); method.print_cpp_arg_list(os, [&] (int i, int arg) { ParmVarDecl *param = method.fd->getParamDecl(i); print_arg_conversion(param, method.get_param(i)); }); osprintf(os, ";\n"); osprintf(os, "}\n"); } /* Print a definition for a constructor for the "id" class * that takes a user object. * * The user object is taken as a std::any and copied into * a new std::any object on the heap. * A pointer to this heap object is stored in the isl_id and * is scheduled to be freed when the reference count of the isl_id * drops to zero. * If the allocation of the isl_id fails, then the heap object * will not be freed automatically, so it needs to be freed manually. * * Unless checked C++ bindings are being generated, * the ctx argument is copied into the save_ctx variable * for use by print_throw_last_error, which throws an exception * if the construction fails. * During the function call, isl is made not to print any error message * because the error message is included in the exception. */ void plain_cpp_generator::impl_printer::print_id_constructor_user() { print_id_constructor_user_header(); os << "{\n"; if (!generator.checked) { print_save_ctx("ctx"); print_on_error_continue(); } os << " std::any *p = new std::any(any);\n"; os << " auto res = isl_id_alloc(ctx.get(), str.c_str(), p);\n"; os << " res = isl_id_set_free_user(res, &ctx::free_user);\n"; os << " if (!res) {\n"; os << " delete p;\n"; if (!generator.checked) print_throw_last_error(os); os << " }\n"; os << " ptr = res;\n"; os << "}\n"; } /* Print a definition for an "id" method * for retrieving the user object associated to the identifier. * If "optional" is set, the method returns a std::optional user object. * The returned object is of a type specified by template parameter T. * * The isl_id needs to have been created by the constructor generated * by print_id_constructor_user. That is, it needs to have a user pointer and * it needs to have its free_user callback set to &ctx::free_user. * The object stored in the std::any also needs to be of the required type. * * If "optional" is set, return a std::nullopt if any of the checks fail. * Otherwise, throw an exception_invalid (or call isl_die and * return a default T in the checked C++ bindings). */ void plain_cpp_generator::impl_printer::print_id_user(bool optional) { auto fail = [&] (const char *msg) { if (optional) os << " return std::nullopt;\n"; else generator.print_invalid(os, 4, msg, "return T()"); }; os << "\n"; print_id_user_header(optional); os << "{\n"; print_check_ptr("ptr"); os << " std::any *p = (std::any *) isl_id_get_user(ptr);\n"; os << " if (!p)\n"; fail("no user pointer"); os << " if (isl_id_get_free_user(ptr) != &ctx::free_user)\n"; fail("user pointer not attached by C++ interface"); os << " T *res = std::any_cast(p);\n"; os << " if (!res)\n"; fail("user pointer not of given type"); os << " return *res;\n"; os << "}\n"; } /* Print implementation of copy assignment operator. * * If the class has any persistent callbacks, then copy them * from the original object. */ void plain_cpp_generator::impl_printer::print_copy_assignment() { const char *name = clazz.name.c_str(); const char *cppname = cppstring.c_str(); osprintf(os, "\n"); osprintf(os, "%s &%s::operator=(%s obj) {\n", cppname, cppname, cppname); osprintf(os, " std::swap(this->ptr, obj.ptr);\n", name); if (clazz.has_persistent_callbacks()) osprintf(os, " copy_callbacks(obj);\n"); osprintf(os, " return *this;\n"); osprintf(os, "}\n"); } /* Print implementation of destructor. * * No explicit destructor is needed for type based subclasses. */ void plain_cpp_generator::impl_printer::print_destructor() { const char *name = clazz.name.c_str(); const char *cppname = cppstring.c_str(); if (clazz.is_type_subclass()) return; osprintf(os, "\n"); osprintf(os, "%s::~%s() {\n", cppname, cppname); osprintf(os, " if (ptr)\n"); osprintf(os, " %s_free(ptr);\n", name); osprintf(os, "}\n"); } /* Print a check that the persistent callback corresponding to "fd" * is not set, throwing an exception (or printing an error message * and returning nullptr) if it is set. */ void plain_cpp_generator::print_check_no_persistent_callback(ostream &os, const isl_class &clazz, FunctionDecl *fd) { string callback_name = clazz.persistent_callback_name(fd); osprintf(os, " if (%s_data)\n", callback_name.c_str()); print_invalid(os, 4, "cannot release object with persistent callbacks", "return nullptr"); } /* Print implementation of ptr() functions. * Since type based subclasses share the pointer with their superclass, * they can also reuse these functions from the superclass. * * If an object has persistent callbacks set, then the underlying * C object pointer cannot be released because it references data * in the C++ object. */ void plain_cpp_generator::impl_printer::print_ptr() { const char *name = clazz.name.c_str(); const char *cppname = cppstring.c_str(); set::const_iterator in; const set &callbacks = clazz.persistent_callbacks; if (clazz.is_type_subclass()) return; osprintf(os, "\n"); osprintf(os, "__isl_give %s *%s::copy() const & {\n", name, cppname); osprintf(os, " return %s_copy(ptr);\n", name); osprintf(os, "}\n\n"); osprintf(os, "__isl_keep %s *%s::get() const {\n", name, cppname); osprintf(os, " return ptr;\n"); osprintf(os, "}\n\n"); osprintf(os, "__isl_give %s *%s::release() {\n", name, cppname); for (in = callbacks.begin(); in != callbacks.end(); ++in) generator.print_check_no_persistent_callback(os, clazz, *in); osprintf(os, " %s *tmp = ptr;\n", name); osprintf(os, " ptr = nullptr;\n"); osprintf(os, " return tmp;\n"); osprintf(os, "}\n\n"); osprintf(os, "bool %s::is_null() const {\n", cppname); osprintf(os, " return ptr == nullptr;\n"); osprintf(os, "}\n"); } /* Print implementations for the "as" and "isa" methods, if the printed class * is a superclass with a type function. * * "isa" checks whether an object is of a given subclass type. * "isa_type" does the same, but gets passed the value of the type field * of the subclass as a function argument and the type of this field * as a template argument. * "as" casts an object to a given subclass type, erroring out * if the object is not of the given type. * * If the input is an invalid object, then these methods raise * an exception. * If checked bindings are being generated, * then an invalid boolean or object is returned instead. */ void plain_cpp_generator::impl_printer::print_downcast() { const char *cppname = cppstring.c_str(); if (!clazz.fn_type) return; osprintf(os, "\n"); osprintf(os, "template \n"); osprintf(os, "%s %s::isa_type(T subtype) const\n", generator.isl_bool2cpp().c_str(), cppname); osprintf(os, "{\n"); osprintf(os, " if (is_null())\n"); if (generator.checked) osprintf(os, " return boolean();\n"); else print_throw_NULL_input(os); osprintf(os, " return %s(get()) == subtype;\n", clazz.fn_type->getNameAsString().c_str()); osprintf(os, "}\n"); osprintf(os, "template \n"); osprintf(os, "%s %s::isa() const\n", generator.isl_bool2cpp().c_str(), cppname); osprintf(os, "{\n"); osprintf(os, " return isa_type(T::type);\n"); osprintf(os, "}\n"); osprintf(os, "template \n"); osprintf(os, "T %s::as() const\n", cppname); osprintf(os, "{\n"); if (generator.checked) osprintf(os, " if (isa().is_false())\n"); else osprintf(os, " if (!isa())\n"); generator.print_invalid(os, 4, "not an object of the requested subtype", "return T()"); osprintf(os, " return T(copy());\n"); osprintf(os, "}\n"); } /* Print the implementation of the ctx method. */ void plain_cpp_generator::impl_printer::print_ctx() { const char *name = clazz.name.c_str(); const char *cppname = cppstring.c_str(); std::string ns = generator.isl_namespace(); osprintf(os, "\n"); osprintf(os, "%sctx %s::ctx() const {\n", ns.c_str(), cppname); osprintf(os, " return %sctx(%s_get_ctx(ptr));\n", ns.c_str(), name); osprintf(os, "}\n"); } /* Print a separator between groups of method definitions. * * No additional separator is required between method definitions. */ void plain_cpp_generator::impl_printer::print_method_separator() { } /* Print the implementations of the methods needed for the persistent callbacks * of the class. */ void plain_cpp_generator::impl_printer::print_persistent_callbacks() { const char *cppname = cppstring.c_str(); string classname = type2cpp(clazz); if (!clazz.has_persistent_callbacks()) return; osprintf(os, "\n"); osprintf(os, "%s &%s::copy_callbacks(const %s &obj)\n", cppname, classname.c_str(), cppname); osprintf(os, "{\n"); for (const auto &callback : clazz.persistent_callbacks) { string callback_name = clazz.persistent_callback_name(callback); osprintf(os, " %s_data = obj.%s_data;\n", callback_name.c_str(), callback_name.c_str()); } osprintf(os, " return *this;\n"); osprintf(os, "}\n"); for (const auto &callback : clazz.persistent_callbacks) print_set_persistent_callback(Method(clazz, callback)); } /* Print a definition for the "get" method "fd" in class "clazz", * using a name that includes the "get_" prefix, to "os". * * This definition simply calls the variant without the "get_" prefix and * returns its result. * Note that static methods are not considered to be "get" methods. */ void plain_cpp_generator::impl_printer::print_get_method(FunctionDecl *fd) { string get_name = clazz.base_method_name(fd); string name = clazz.method_name(fd); int num_params = fd->getNumParams(); osprintf(os, "\n"); print_full_method_header(Method(clazz, fd, get_name)); osprintf(os, "{\n"); osprintf(os, " return %s(", name.c_str()); for (int i = 1; i < num_params; ++i) { ParmVarDecl *param = fd->getParamDecl(i); if (i != 1) osprintf(os, ", "); osprintf(os, "%s", param->getName().str().c_str()); } osprintf(os, ");\n"); osprintf(os, "}\n"); } /* Print code that checks that all isl object arguments to "method" are valid * (not NULL) and throws an exception if they are not. * * If checked bindings are being generated, * then no such check is performed. */ void plain_cpp_generator::impl_printer::print_argument_validity_check( const Method &method) { int n; bool first = true; if (generator.checked) return; n = method.num_params(); for (int i = 0; i < n; ++i) { bool is_this; ParmVarDecl *param = method.fd->getParamDecl(i); string name = param->getName().str(); const char *name_str = name.c_str(); QualType type = param->getOriginalType(); is_this = i == 0 && method.kind == Method::Kind::member_method; if (!is_this && (is_isl_ctx(type) || !is_isl_type(type))) continue; if (first) osprintf(os, " if ("); else osprintf(os, " || "); if (is_this) osprintf(os, "!ptr"); else osprintf(os, "%s.is_null()", name_str); first = false; } if (first) return; osprintf(os, ")\n"); print_throw_NULL_input(os); } /* Print code for saving a copy of "ctx" in a "saved_ctx" variable. */ void plain_cpp_generator::impl_printer::print_save_ctx(const std::string &ctx) { os << " auto saved_ctx = " << ctx << ";\n"; } /* Print code for saving a copy of the isl::ctx available at the start * of the method "method" in a "saved_ctx" variable, * for use in exception handling. * * If checked bindings are being generated, * then the "saved_ctx" variable is not needed. * If "method" is a member function, then obtain the isl_ctx from * the "this" object. * If the first argument of the method is an isl::ctx, then use that one. * Otherwise, save a copy of the isl::ctx associated to the first argument * of isl object type. */ void plain_cpp_generator::impl_printer::print_save_ctx(const Method &method) { int n; ParmVarDecl *param = method.fd->getParamDecl(0); QualType type = param->getOriginalType(); if (generator.checked) return; if (method.kind == Method::Kind::member_method) return print_save_ctx("ctx()"); if (is_isl_ctx(type)) return print_save_ctx(param->getName().str()); n = method.num_params(); for (int i = 0; i < n; ++i) { ParmVarDecl *param = method.fd->getParamDecl(i); QualType type = param->getOriginalType(); if (!is_isl_type(type)) continue; print_save_ctx(param->getName().str() + ".ctx()"); return; } } /* Print code to make isl not print an error message when an error occurs * within the current scope (if exceptions are available), * since the error message will be included in the exception. * If exceptions are not available, then exception::on_error * is set to ISL_ON_ERROR_ABORT and isl is therefore made to abort instead. * * If checked bindings are being generated, * then leave it to the user to decide what isl should do on error. * Otherwise, assume that a valid isl::ctx is available * in the "saved_ctx" variable, * e.g., through a prior call to print_save_ctx. */ void plain_cpp_generator::impl_printer::print_on_error_continue() { if (generator.checked) return; osprintf(os, " options_scoped_set_on_error saved_on_error(saved_ctx, " "exception::on_error);\n"); } /* Print code to "os" that checks whether any of the persistent callbacks * of the class of "method" is set and if it failed with an exception. * If so, the "eptr" in the corresponding data structure contains the exception * that was caught and that needs to be rethrown. * This field is cleared because the callback and its data may get reused. * * The check only needs to be generated for member methods since * an object is needed for any of the persistent callbacks to be set. */ static void print_persistent_callback_exceptional_execution_check(ostream &os, const Method &method) { if (method.kind != Method::Kind::member_method) return; for (const auto &pcb : method.clazz.persistent_callbacks) { auto callback_name = method.clazz.persistent_callback_name(pcb); osprintf(os, " if (%s_data && %s_data->eptr) {\n", callback_name.c_str(), callback_name.c_str()); osprintf(os, " std::exception_ptr eptr = %s_data->eptr;\n", callback_name.c_str()); osprintf(os, " %s_data->eptr = nullptr;\n", callback_name.c_str()); osprintf(os, " std::rethrow_exception(eptr);\n"); osprintf(os, " }\n"); } } /* Print code that checks whether the execution of the core of "method" * was successful. * * If checked bindings are being generated, * then no checks are performed. * * Otherwise, first check if any of the callbacks failed with * an exception. If so, the "eptr" in the corresponding data structure * contains the exception that was caught and that needs to be rethrown. * Then check if the function call failed in any other way and throw * the appropriate exception. * In particular, if the return type is isl_stat, isl_bool or isl_size, * then a negative value indicates a failure. If the return type * is an isl type, then a NULL value indicates a failure. * Assume print_save_ctx has made sure that a valid isl::ctx * is available in the "ctx" variable. */ void plain_cpp_generator::impl_printer::print_exceptional_execution_check( const Method &method) { bool check_null, check_neg; QualType return_type = method.fd->getReturnType(); if (generator.checked) return; print_persistent_callback_exceptional_execution_check(os, method); for (const auto &callback : method.callbacks) { std::string name; name = callback->getName().str(); osprintf(os, " if (%s_data.eptr)\n", name.c_str()); osprintf(os, " std::rethrow_exception(%s_data.eptr);\n", name.c_str()); } check_neg = is_isl_neg_error(return_type); check_null = is_isl_type(return_type); if (!check_null && !check_neg) return; if (check_neg) osprintf(os, " if (res < 0)\n"); else osprintf(os, " if (!res)\n"); print_throw_last_error(os); } /* Return a pointer to the appropriate type printer, * i.e., the regular type printer or the checked type printer * depending on the setting of this->checked. */ std::unique_ptr plain_cpp_generator::type_printer() { cpp_type_printer *printer; if (checked) printer = new checked_cpp_type_printer(); else printer = new cpp_type_printer(); return std::unique_ptr(printer); } /* Return the C++ return type of the method "method". * * Use the appropriate type printer. */ std::string plain_cpp_generator::get_return_type(const Method &method) { return type_printer()->return_type(method); } /* Given a method "method" for setting a persistent callback of its class, * print the implementations of the methods needed for that callback. * * In particular, print * - the implementation of a static inline method * for use as the C callback function * - the definition of a private method for setting the callback function * - the public method for constructing a new object with the callback set. */ void plain_cpp_generator::impl_printer::print_set_persistent_callback( const Method &method) { string fullname = method.fd->getName().str(); ParmVarDecl *param = persistent_callback_arg(method.fd); string pname; string callback_name = clazz.persistent_callback_name(method.fd); osprintf(os, "\n"); print_persistent_callback_prototype(method.fd); osprintf(os, "\n"); osprintf(os, "{\n"); print_callback_body(2, param, callback_name); osprintf(os, "}\n\n"); pname = param->getName().str(); print_persistent_callback_setter_prototype(method.fd); osprintf(os, "\n"); osprintf(os, "{\n"); print_check_ptr_start("ptr"); osprintf(os, " %s_data = std::make_shared();\n", callback_name.c_str(), callback_name.c_str()); osprintf(os, " %s_data->func = %s;\n", callback_name.c_str(), pname.c_str()); osprintf(os, " ptr = %s(ptr, &%s, %s_data.get());\n", fullname.c_str(), callback_name.c_str(), callback_name.c_str()); print_check_ptr_end("ptr"); osprintf(os, "}\n\n"); print_full_method_header(method); osprintf(os, "{\n"); osprintf(os, " auto copy = *this;\n"); osprintf(os, " copy.set_%s_data(%s);\n", callback_name.c_str(), pname.c_str()); osprintf(os, " return copy;\n"); osprintf(os, "}\n"); } /* Print the return statement of the C++ method "method". * * The result of the corresponding isl function is returned as a new * object if the underlying isl function returns an isl_* ptr, as a bool * if the isl function returns an isl_bool, as void if the isl functions * returns an isl_stat, * as std::string if the isl function returns 'const char *', and as * unmodified return value otherwise. * If checked C++ bindings are being generated, * then an isl_bool return type is transformed into a boolean and * an isl_stat into a stat since no exceptions can be generated * on negative results from the isl function. * If the method returns a new instance of the same object type and * if the class has any persistent callbacks, then the data * for these callbacks are copied from the original to the new object. * If "clazz" is a subclass that is based on a type function and * if the return type corresponds to the superclass data type, * then it is replaced by the subclass data type. */ void plain_cpp_generator::impl_printer::print_method_return( const Method &method) { QualType return_type = method.fd->getReturnType(); string rettype_str = generator.get_return_type(method); bool returns_super = method.is_subclass_mutator(); if (is_isl_type(return_type) || (generator.checked && is_isl_neg_error(return_type))) { osprintf(os, " return manage(res)"); if (is_mutator(clazz, method.fd) && clazz.has_persistent_callbacks()) osprintf(os, ".copy_callbacks(*this)"); if (returns_super) osprintf(os, ".as<%s>()", rettype_str.c_str()); osprintf(os, ";\n"); } else if (is_isl_stat(return_type)) { osprintf(os, " return;\n"); } else if (is_string(return_type)) { osprintf(os, " std::string tmp(res);\n"); if (gives(method.fd)) osprintf(os, " free(res);\n"); osprintf(os, " return tmp;\n"); } else { osprintf(os, " return res;\n"); } } /* Print the header for "method", including the terminating semicolon * in case of a declaration and a newline. * * Use the appropriate type printer to print argument and return types. */ void plain_cpp_generator::plain_printer::print_full_method_header( const Method &method) { auto type_printer = generator.type_printer(); print_method_header(method, *type_printer); if (declarations) osprintf(os, ";"); osprintf(os, "\n"); } /* Generate the list of argument types for a callback function of * type "type". If "cpp" is set, then generate the C++ type list, otherwise * the C type list. * * Use the appropriate type printer. * For the plain C++ interface, the argument position is irrelevant, * so simply pass in -1. */ string plain_cpp_generator::generate_callback_args(QualType type, bool cpp) { return type_printer()->generate_callback_args(-1, type, cpp); } /* Generate the full cpp type of a callback function of type "type". * * Use the appropriate type printer. * For the plain C++ interface, the argument position is irrelevant, * so simply pass in -1. */ string plain_cpp_generator::generate_callback_type(QualType type) { return type_printer()->generate_callback_type(-1, type); } /* Print the call to the C++ callback function "call", * with the given indentation, wrapped * for use inside the lambda function that is used as the C callback function, * in the case where checked C++ bindings are being generated. * * In particular, print * * auto ret = @call@; * return ret.release(); */ void plain_cpp_generator::impl_printer::print_wrapped_call_checked(int indent, const string &call) { osprintf(os, indent, "auto ret = %s;\n", call.c_str()); osprintf(os, indent, "return ret.release();\n"); } /* Print the call to the C++ callback function "call", * with the given indentation and with return type "rtype", wrapped * for use inside the lambda function that is used as the C callback function. * * In particular, print * * ISL_CPP_TRY { * @call@; * return isl_stat_ok; * } ISL_CPP_CATCH_ALL { * data->eptr = std::current_exception(); * return isl_stat_error; * } * or * ISL_CPP_TRY { * auto ret = @call@; * return ret ? isl_bool_true : isl_bool_false; * } ISL_CPP_CATCH_ALL { * data->eptr = std::current_exception(); * return isl_bool_error; * } * or * ISL_CPP_TRY { * auto ret = @call@; * return ret.release(); * } ISL_CPP_CATCH_ALL { * data->eptr = std::current_exception(); * return NULL; * } * * depending on the return type. * * where ISL_CPP_TRY is defined to "try" and ISL_CPP_CATCH_ALL to "catch (...)" * (if exceptions are available). * * If checked C++ bindings are being generated, then * the call is wrapped differently. */ void plain_cpp_generator::impl_printer::print_wrapped_call(int indent, const string &call, QualType rtype) { if (generator.checked) return print_wrapped_call_checked(indent, call); osprintf(os, indent, "ISL_CPP_TRY {\n"); if (is_isl_stat(rtype)) osprintf(os, indent, " %s;\n", call.c_str()); else osprintf(os, indent, " auto ret = %s;\n", call.c_str()); if (is_isl_stat(rtype)) osprintf(os, indent, " return isl_stat_ok;\n"); else if (is_isl_bool(rtype)) osprintf(os, indent, " return ret ? isl_bool_true : isl_bool_false;\n"); else osprintf(os, indent, " return ret.release();\n"); osprintf(os, indent, "} ISL_CPP_CATCH_ALL {\n"); osprintf(os, indent, " data->eptr = std::current_exception();\n"); if (is_isl_stat(rtype)) osprintf(os, indent, " return isl_stat_error;\n"); else if (is_isl_bool(rtype)) osprintf(os, indent, " return isl_bool_error;\n"); else osprintf(os, indent, " return NULL;\n"); osprintf(os, indent, "}\n"); } /* Print the declaration for a "prefix"_data data structure * that can be used for passing to a C callback function * containing a copy of the C++ callback function "param", * along with an std::exception_ptr that is used to store any * exceptions thrown in the C++ callback. * * If the C callback is of the form * * isl_stat (*fn)(__isl_take isl_map *map, void *user) * * then the following declaration is printed: * * struct _data { * std::function func; * std::exception_ptr eptr; * } * * (without a newline or a semicolon). * * The std::exception_ptr object is not added to "prefix"_data * if checked C++ bindings are being generated. */ void plain_cpp_generator::plain_printer::print_callback_data_decl( ParmVarDecl *param, const string &prefix) { string cpp_args; cpp_args = generator.generate_callback_type(param->getType()); osprintf(os, " struct %s_data {\n", prefix.c_str()); osprintf(os, " %s func;\n", cpp_args.c_str()); if (!generator.checked) osprintf(os, " std::exception_ptr eptr;\n"); osprintf(os, " }"); } /* Given a group of methods with the same name, * should extra methods be added that take as arguments * those types that can be converted to the original argument type * through a unary constructor? * * Note that even if this method returns true, * the extra methods are only printed by the caller * if exactly one of the methods in the group was originally defined * in the printed class. * Signal that they should be printed if the group contains * both methods originally defined in the printed class and * methods that have been copied from an ancestor * by checking whether there are at least two methods in the group. */ bool plain_cpp_generator::plain_printer::want_descendent_overloads( const function_set &methods) { return methods.size() > 1; } /* Print the header of the constructor for the "id" class * that takes a user object. * * The user object is taken as a std::any. */ void plain_cpp_generator::plain_printer::print_id_constructor_user_header() { if (declarations) os << " inline explicit "; else os << "id::"; os << "id(" << generator.isl_namespace() << "ctx ctx, " << "const std::string &str, const std::any &any)"; if (declarations) os << ";"; os << "\n"; } /* Print the header of the "id" method * for retrieving the user object associated to the identifier. * If "optional" is set, the method returns a std::optional user object. * The returned object is of a type specified by template parameter T. */ void plain_cpp_generator::plain_printer::print_id_user_header(bool optional) { auto indent = declarations ? " " : ""; os << indent << "template \n"; os << indent << (optional ? "std::optional " : "T "); if (!declarations) os << "id::"; os << (optional ? "try_" : ""); os << "user() const"; if (declarations) os << ";"; os << "\n"; } /* Perform printing by "fn" in a context that only gets compiled * by C++17 compilers. */ static void on_cplusplus17(ostream &os, const std::function &fn) { os << "#if __cplusplus >= 201703L\n"; fn(); os << "#endif\n"; } /* Print declarations or definitions of the special methods of the "id" class * that are not automatically derived from the C interface. * * In particular, print a constructor that takes a user pointer * as well as methods for retrieving this user pointer. * * These methods require C++17 features. */ void plain_cpp_generator::plain_printer::print_special_id() { os << "\n"; on_cplusplus17(os, [this] () { print_id_constructor_user(); print_id_user(true); print_id_user(false); }); } /* Print declarations or definitions of any special methods of this class * not automatically derived from the C interface. * * In particular, print special methods for the "id" class. */ void plain_cpp_generator::plain_printer::print_special() { if (clazz.name == "isl_id") print_special_id(); } /* Print declarations or definitions of the public methods. */ void plain_cpp_generator::plain_printer::print_public_methods() { print_public_constructors(); print_constructors(); print_copy_assignment(); print_destructor(); print_ptr(); print_downcast(); print_ctx(); print_method_separator(); print_persistent_callbacks(); print_methods(); print_set_enums(); print_special(); } /* Print the body of C function callback with the given indentation * that can be use as an argument to "param" for marshalling * the corresponding C++ callback. * The data structure that contains the C++ callback is of type * "prefix"_data. * * For a callback of the form * * isl_stat (*fn)(__isl_take isl_map *map, void *user) * * the following code is generated: * * auto *data = static_cast_data *>(arg_1); * ISL_CPP_TRY { * stat ret = (data->func)(manage(arg_0)); * return isl_stat_ok; * } ISL_CPP_CATCH_ALL { * data->eptr = std::current_exception(); * return isl_stat_error; * } * * If checked C++ bindings are being generated, then * generate the following code: * * auto *data = static_cast_data *>(arg_1); * stat ret = (data->func)(manage(arg_0)); * return isl_stat(ret); */ void plain_cpp_generator::impl_printer::print_callback_body(int indent, ParmVarDecl *param, const string &prefix) { QualType ptype, rtype; string call, last_idx; const FunctionProtoType *callback; int num_params; ptype = param->getType(); callback = extract_prototype(ptype); rtype = callback->getReturnType(); num_params = callback->getNumArgs(); last_idx = ::to_string(num_params - 1); call = "(data->func)("; for (long i = 0; i < num_params - 1; i++) { if (!generator.callback_takes_argument(param, i)) call += "manage_copy"; else call += "manage"; call += "(arg_" + ::to_string(i) + ")"; if (i != num_params - 2) call += ", "; } call += ")"; osprintf(os, indent, "auto *data = static_cast(arg_%s);\n", prefix.c_str(), last_idx.c_str()); print_wrapped_call(indent, call, rtype); } /* Print the local variables that are needed for a callback argument, * in particular, print a lambda function that wraps the callback and * a pointer to the actual C++ callback function. * * For a callback of the form * * isl_stat (*fn)(__isl_take isl_map *map, void *user) * * the following lambda function is generated: * * auto fn_lambda = [](isl_map *arg_0, void *arg_1) -> isl_stat { * auto *data = static_cast(arg_1); * try { * stat ret = (data->func)(manage(arg_0)); * return isl_stat_ok; * } catch (...) { * data->eptr = std::current_exception(); * return isl_stat_error; * } * }; * * A copy of the std::function C++ callback function is stored in * a fn_data data structure for passing to the C callback function, * along with an std::exception_ptr that is used to store any * exceptions thrown in the C++ callback. * * struct fn_data { * std::function func; * std::exception_ptr eptr; * } fn_data = { fn }; * * This std::function object represents the actual user * callback function together with the locally captured state at the caller. * * The lambda function is expected to be used as a C callback function * where the lambda itself is provided as the function pointer and * where the user void pointer is a pointer to fn_data. * The std::function object is extracted from the pointer to fn_data * inside the lambda function. * * The std::exception_ptr object is not added to fn_data * if checked C++ bindings are being generated. * The body of the generated lambda function then is as follows: * * stat ret = (data->func)(manage(arg_0)); * return isl_stat(ret); * * If the C callback does not take its arguments, then * manage_copy is used instead of manage. */ void plain_cpp_generator::impl_printer::print_callback_local(ParmVarDecl *param) { string pname; QualType ptype, rtype; string c_args, cpp_args, rettype; const FunctionProtoType *callback; pname = param->getName().str(); ptype = param->getType(); c_args = generator.generate_callback_args(ptype, false); callback = extract_prototype(ptype); rtype = callback->getReturnType(); rettype = rtype.getAsString(); print_callback_data_decl(param, pname); osprintf(os, " %s_data = { %s };\n", pname.c_str(), pname.c_str()); osprintf(os, " auto %s_lambda = [](%s) -> %s {\n", pname.c_str(), c_args.c_str(), rettype.c_str()); print_callback_body(4, param, pname); osprintf(os, " };\n"); } /* Return the C++ counterpart to the isl_bool type. * * For the checked C++ bindings this is "boolean". */ std::string checked_cpp_type_printer::isl_bool() const { return "boolean"; } /* Return the C++ counterpart to the isl_bool type. * * Use the appropriate type printer. */ string plain_cpp_generator::isl_bool2cpp() { return type_printer()->isl_bool(); } /* Return the C++ counterpart to the isl_stat type. * * For the checked C++ bindings this is "stat". */ string checked_cpp_type_printer::isl_stat() const { return "stat"; } /* Return the C++ counterpart to the isl_size type. * * For the checked C++ bindings this is "class size". */ string checked_cpp_type_printer::isl_size() const { return "class size"; } /* Return the namespace of the generated C++ bindings. * * For the checked C++ bindings this is "isl::checked::". */ std::string checked_cpp_type_printer::isl_namespace() const { return "isl::checked::"; } /* Return the namespace of the generated C++ bindings. * * Use the appropriate type printer. */ string plain_cpp_generator::isl_namespace() { return type_printer()->isl_namespace(); } /* Translate parameter or return type "type" to its C++ name counterpart. * * Use the appropriate type printer. * For the plain C++ interface, the argument position is irrelevant, * so simply pass in -1. */ string plain_cpp_generator::param2cpp(QualType type) { return type_printer()->param(-1, type); }