diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 06a0353a..ab6699ff 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -23,6 +23,9 @@ add_clang_executable(binder enum.hpp enum.cpp + const.hpp + const.cpp + function.hpp function.cpp diff --git a/source/binder.cpp b/source/binder.cpp index e409dc2f..cdd9e223 100644 --- a/source/binder.cpp +++ b/source/binder.cpp @@ -14,7 +14,41 @@ #include // is_python_builtin -#include +#include +#include +#include +#include +#include + +#include // Declares llvm::cl::extrahelp + +#include +#include +#include +#include +#include +#include + +using namespace clang::tooling; +using namespace llvm; + + +// Apply a custom category to all command-line options so that they are the +// only ones displayed. +static llvm::cl::OptionCategory BinderToolCategory("Binder options"); + +// CommonOptionsParser declares HelpMessage with a description of the common +// command-line options related to the compilation database and input files. +// It's nice to have this help message in all tools. +static cl::extrahelp CommonHelp(CommonOptionsParser::HelpMessage); + +// A help message for this specific tool can be added afterwards. +static cl::extrahelp MoreHelp("\nMore help text...\n"); + + +using binder::Config; + +using namespace clang; using std::string; using std::vector; diff --git a/source/const.cpp b/source/const.cpp new file mode 100644 index 00000000..de61a7e9 --- /dev/null +++ b/source/const.cpp @@ -0,0 +1,114 @@ +// -*- mode:c++;tab-width:2;indent-tabs-mode:t;show-trailing-whitespace:t;rm-trailing-spaces:t -*- +// vi: set ts=2 noet: +// +// Copyright (c) 2021 Sergey Lyskov +// +// All rights reserved. Use of this source code is governed by a +// MIT license that can be found in the LICENSE file. + +/// @file binder/const.cpp +/// @brief Binding generation for C++ constants +/// @author Sergey Lyskov, Andrii Verbytskyi + + +#include + +#include +#include + +#include + +#include + +using namespace llvm; +using namespace clang; +#include +using std::string; +using std::vector; + +using namespace fmt::literals; + +namespace binder { + +/// check if generator can create binding +bool is_bindable(VarDecl const *E) +{ + if ( !E->getType().isConstQualified() ) return false; + if ( !E->hasInit() ) return false; + if ( E->getType().getTypePtr()->isArrayType()) return false; + if ( E->isCXXInstanceMember() or E->isCXXClassMember() ) return false; + if ( E->isCXXInstanceMember() ) return false; + if ( standard_name( E->getType().getCanonicalType().getAsString() ) == "const std::string" ) return true; + if ( E->getType().getTypePtr()->isRealFloatingType() ) return true; + if ( E->getType().getTypePtr()->isIntegerType() ) return true; + if ( E->getType().getTypePtr()->isBooleanType() ) return true; + return false; +} + +// Generate binding for given function: py::enum_(module, "MyEnum")... +std::string bind_const(std::string const & module, VarDecl const *E) +{ + string r="\t"; + clang::Expr const* init = E->getInit(); + if ( init ){ + string name { E->getNameAsString() }; + std::string type = E->getType().getCanonicalType().getAsString(); + std::string pytype = ""; + bool pytype_set = false; + //This is a list of types that can be binded with pybind, see https://pybind11.readthedocs.io/en/stable/advanced/pycpp/object.html + if ( !pytype_set and standard_name( type ) == "const std::string" ) { pytype_set = true; pytype = "str";} + if ( !pytype_set and E->getType().getTypePtr()->isRealFloatingType() ) { pytype_set = true; pytype = "float_"; } + if ( !pytype_set and E->getType().getTypePtr()->isIntegerType() ) { pytype_set = true; pytype = "int_"; } + if ( !pytype_set and E->getType().getTypePtr()->isBooleanType() ) { pytype_set = true; pytype = "bool_"; } + if ( pytype_set ) { + std::string rhs; + llvm::raw_string_ostream rso(rhs); + clang::LangOptions lang_opts; + lang_opts.CPlusPlus = true; + clang::PrintingPolicy Policy(lang_opts); + init->printPretty(rso, 0, Policy); + r = "\t{}.attr(\"{}\") = pybind11::{}({})\n"_format( module, name, pytype, rhs); + } + } + r.pop_back(); + return r; +} + + +/// Generate string id that uniquly identify C++ binding object. For functions this is function prototype and for classes forward declaration. +string ConstBinder::id() const +{ + return E->getQualifiedNameAsString(); +} + + +/// check if generator can create binding +bool ConstBinder::bindable() const +{ + return is_bindable(E); +} + +/// check if user requested binding for the given declaration +void ConstBinder::request_bindings_and_skipping(Config const &config) +{ + if( config.is_namespace_binding_requested( namespace_from_named_decl(E) ) ) Binder::request_bindings(); +} + + +/// extract include needed for this generator and add it to includes vector +void ConstBinder::add_relevant_includes(IncludeSet &includes) const +{ +} + +/// generate binding code for this object and all its dependencies +void ConstBinder::bind(Context &context) +{ + if( is_binded() ) return; + + string const module_variable_name = context.module_variable_name( namespace_from_named_decl(E) ); + + code() = "\t" + generate_comment_for_declaration(E); + code() += bind_const(module_variable_name, E) + ";\n\n"; +} + +} // namespace binder diff --git a/source/const.hpp b/source/const.hpp new file mode 100644 index 00000000..f5c9ed58 --- /dev/null +++ b/source/const.hpp @@ -0,0 +1,63 @@ +// -*- mode:c++;tab-width:2;indent-tabs-mode:t;show-trailing-whitespace:t;rm-trailing-spaces:t -*- +// vi: set ts=2 noet: +// +// Copyright (c) 2021 Sergey Lyskov +// +// All rights reserved. Use of this source code is governed by a +// MIT license that can be found in the LICENSE file. + +/// @file binder/const.hpp +/// @brief Binding generation for C++ constant expressions +/// @author Sergey Lyskov, Andrii Verbytskyi + + +#ifndef _INCLUDED_const_hpp_ +#define _INCLUDED_const_hpp_ + +#include + +#include + +#include + +namespace binder { + +/// check if generator can create binding +bool is_bindable(clang::VarDecl const *E); + + +// Generate binding for given function +std::string bind_const(std::string const & module, clang::VarDecl const *E); + + +class ConstBinder : public Binder +{ +public: + ConstBinder(clang::VarDecl const *e) : E(e) {} + + /// Generate string id that uniquly identify C++ binding object. For functions this is function prototype and for classes forward declaration. + string id() const override; + // return Clang AST NamedDecl pointer to original declaration used to create this Binder + clang::NamedDecl const * named_decl() const override { return E; }; + + /// check if generator can create binding + bool bindable() const override; + + /// check if user requested binding for the given declaration + virtual void request_bindings_and_skipping(Config const &) override; + + /// extract include needed for this generator and add it to includes vector + void add_relevant_includes(IncludeSet &includes) const override; + + /// generate binding code for this object and all its dependencies + void bind(Context &) override; + + +private: + clang::VarDecl const *E; +}; + + +} // namespace binder + +#endif // _INCLUDED_const_hpp_ diff --git a/test/T60.const.hpp b/test/T60.const.hpp new file mode 100644 index 00000000..b054e92c --- /dev/null +++ b/test/T60.const.hpp @@ -0,0 +1,48 @@ +// -*- mode:c++;tab-width:2;indent-tabs-mode:t;show-trailing-whitespace:t;rm-trailing-spaces:t -*- +// vi: set ts=2 noet: +// +// Copyright (c) 2016 Sergey Lyskov +// +// All rights reserved. Use of this source code is governed by a +// MIT license that can be found in the LICENSE file. + +/// @file binder/test/T00.basic.hpp +/// @brief Binder self-test file. Basic functionality. +/// @author Sergey Lyskov + +#ifndef _INCLUDED_T60_basic_hpp_ +#define _INCLUDED_T60_basic_hpp_ +#include +#include + +int const global_int = 1; +long const global_long = 2; + +unsigned int const global_unsigned_int = 3; +unsigned long const global_unsigned_long = 4; + +float const global_float = 5.0; +double const global_double = 6.0; + +std::string const global_string1 = "Some test string"; + +std::string const global_string2 = std::string("Some test string"); + + +double const expression_global_double = 8.0 + 1.0/5.0; + +double const array_global_double_not_binded[5] = {1.0, 2.0, 3.0, 4.0, 5.0}; //This should not appear in bindings so far. + +std::vector const vector_global_double_not_binded{1.0, 2.0, 3.0, 4.0, 5.0}; //This should not appear in bindings so far. + +int foo_char(char *) { return 0; } //This is just for control. + +namespace foo +{ +double const foonamespaced_global_double = 7.0; + +int foonamespaced_foo_char(char *) { return 0; } + +} + +#endif // _INCLUDED_T60_basic_hpp_ diff --git a/test/T60.const.ref b/test/T60.const.ref new file mode 100644 index 00000000..837ad2ca --- /dev/null +++ b/test/T60.const.ref @@ -0,0 +1,119 @@ +// File: T60_const.cpp +#include // foo_char + +#include +#include +#include + +#ifndef BINDER_PYBIND11_TYPE_CASTER + #define BINDER_PYBIND11_TYPE_CASTER + PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr) + PYBIND11_DECLARE_HOLDER_TYPE(T, T*) + PYBIND11_MAKE_OPAQUE(std::shared_ptr) +#endif + +void bind_T60_const(std::function< pybind11::module &(std::string const &namespace_) > &M) +{ + // global_int file:T60.const.hpp line:18 + M("").attr("global_int") = pybind11::int_(1); + + // global_long file:T60.const.hpp line:19 + M("").attr("global_long") = pybind11::int_(2); + + // global_unsigned_int file:T60.const.hpp line:21 + M("").attr("global_unsigned_int") = pybind11::int_(3); + + // global_unsigned_long file:T60.const.hpp line:22 + M("").attr("global_unsigned_long") = pybind11::int_(4); + + // global_float file:T60.const.hpp line:24 + M("").attr("global_float") = pybind11::float_(5.); + + // global_double file:T60.const.hpp line:25 + M("").attr("global_double") = pybind11::float_(6.); + + // global_string1 file:T60.const.hpp line:27 + M("").attr("global_string1") = pybind11::str("Some test string"); + + // global_string2 file:T60.const.hpp line:29 + M("").attr("global_string2") = pybind11::str(std::string("Some test string")); + + // expression_global_double file:T60.const.hpp line:32 + M("").attr("expression_global_double") = pybind11::float_(8. + 1. / 5.); + + // foo_char(char *) file:T60.const.hpp line:38 + M("").def("foo_char", (int (*)(char *)) &foo_char, "C++: foo_char(char *) --> int", pybind11::arg("")); + +} + + +// File: T60_const_1.cpp +#include // foo::foonamespaced_foo_char + +#include +#include +#include + +#ifndef BINDER_PYBIND11_TYPE_CASTER + #define BINDER_PYBIND11_TYPE_CASTER + PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr) + PYBIND11_DECLARE_HOLDER_TYPE(T, T*) + PYBIND11_MAKE_OPAQUE(std::shared_ptr) +#endif + +void bind_T60_const_1(std::function< pybind11::module &(std::string const &namespace_) > &M) +{ + // foo::foonamespaced_global_double file:T60.const.hpp line:42 + M("foo").attr("foonamespaced_global_double") = pybind11::float_(7.); + + // foo::foonamespaced_foo_char(char *) file:T60.const.hpp line:44 + M("foo").def("foonamespaced_foo_char", (int (*)(char *)) &foo::foonamespaced_foo_char, "C++: foo::foonamespaced_foo_char(char *) --> int", pybind11::arg("")); + +} + + +#include +#include +#include +#include +#include + +#include + +typedef std::function< pybind11::module & (std::string const &) > ModuleGetter; + +void bind_T60_const(std::function< pybind11::module &(std::string const &namespace_) > &M); +void bind_T60_const_1(std::function< pybind11::module &(std::string const &namespace_) > &M); + + +PYBIND11_MODULE(T60_const, root_module) { + root_module.doc() = "T60_const module"; + + std::map modules; + ModuleGetter M = [&](std::string const &namespace_) -> pybind11::module & { + auto it = modules.find(namespace_); + if( it == modules.end() ) throw std::runtime_error("Attempt to access pybind11::module for namespace " + namespace_ + " before it was created!!!"); + return it->second; + }; + + modules[""] = root_module; + + std::vector< std::pair > sub_modules { + {"", "foo"}, + }; + for(auto &p : sub_modules ) modules[p.first.size() ? p.first+"::"+p.second : p.second] = modules[p.first].def_submodule(p.second.c_str(), ("Bindings for " + p.first + "::" + p.second + " namespace").c_str() ); + + //pybind11::class_>(M(""), "_encapsulated_data_"); + + bind_T60_const(M); + bind_T60_const_1(M); + +} + +// Source list file: /home/andriish/Projects/binder/test//T60_const.sources +// T60_const.cpp +// T60_const.cpp +// T60_const_1.cpp + +// Modules list file: /home/andriish/Projects/binder/test//T60_const.modules +// foo diff --git a/test/T60_const.cpp b/test/T60_const.cpp new file mode 100644 index 00000000..837ad2ca --- /dev/null +++ b/test/T60_const.cpp @@ -0,0 +1,119 @@ +// File: T60_const.cpp +#include // foo_char + +#include +#include +#include + +#ifndef BINDER_PYBIND11_TYPE_CASTER + #define BINDER_PYBIND11_TYPE_CASTER + PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr) + PYBIND11_DECLARE_HOLDER_TYPE(T, T*) + PYBIND11_MAKE_OPAQUE(std::shared_ptr) +#endif + +void bind_T60_const(std::function< pybind11::module &(std::string const &namespace_) > &M) +{ + // global_int file:T60.const.hpp line:18 + M("").attr("global_int") = pybind11::int_(1); + + // global_long file:T60.const.hpp line:19 + M("").attr("global_long") = pybind11::int_(2); + + // global_unsigned_int file:T60.const.hpp line:21 + M("").attr("global_unsigned_int") = pybind11::int_(3); + + // global_unsigned_long file:T60.const.hpp line:22 + M("").attr("global_unsigned_long") = pybind11::int_(4); + + // global_float file:T60.const.hpp line:24 + M("").attr("global_float") = pybind11::float_(5.); + + // global_double file:T60.const.hpp line:25 + M("").attr("global_double") = pybind11::float_(6.); + + // global_string1 file:T60.const.hpp line:27 + M("").attr("global_string1") = pybind11::str("Some test string"); + + // global_string2 file:T60.const.hpp line:29 + M("").attr("global_string2") = pybind11::str(std::string("Some test string")); + + // expression_global_double file:T60.const.hpp line:32 + M("").attr("expression_global_double") = pybind11::float_(8. + 1. / 5.); + + // foo_char(char *) file:T60.const.hpp line:38 + M("").def("foo_char", (int (*)(char *)) &foo_char, "C++: foo_char(char *) --> int", pybind11::arg("")); + +} + + +// File: T60_const_1.cpp +#include // foo::foonamespaced_foo_char + +#include +#include +#include + +#ifndef BINDER_PYBIND11_TYPE_CASTER + #define BINDER_PYBIND11_TYPE_CASTER + PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr) + PYBIND11_DECLARE_HOLDER_TYPE(T, T*) + PYBIND11_MAKE_OPAQUE(std::shared_ptr) +#endif + +void bind_T60_const_1(std::function< pybind11::module &(std::string const &namespace_) > &M) +{ + // foo::foonamespaced_global_double file:T60.const.hpp line:42 + M("foo").attr("foonamespaced_global_double") = pybind11::float_(7.); + + // foo::foonamespaced_foo_char(char *) file:T60.const.hpp line:44 + M("foo").def("foonamespaced_foo_char", (int (*)(char *)) &foo::foonamespaced_foo_char, "C++: foo::foonamespaced_foo_char(char *) --> int", pybind11::arg("")); + +} + + +#include +#include +#include +#include +#include + +#include + +typedef std::function< pybind11::module & (std::string const &) > ModuleGetter; + +void bind_T60_const(std::function< pybind11::module &(std::string const &namespace_) > &M); +void bind_T60_const_1(std::function< pybind11::module &(std::string const &namespace_) > &M); + + +PYBIND11_MODULE(T60_const, root_module) { + root_module.doc() = "T60_const module"; + + std::map modules; + ModuleGetter M = [&](std::string const &namespace_) -> pybind11::module & { + auto it = modules.find(namespace_); + if( it == modules.end() ) throw std::runtime_error("Attempt to access pybind11::module for namespace " + namespace_ + " before it was created!!!"); + return it->second; + }; + + modules[""] = root_module; + + std::vector< std::pair > sub_modules { + {"", "foo"}, + }; + for(auto &p : sub_modules ) modules[p.first.size() ? p.first+"::"+p.second : p.second] = modules[p.first].def_submodule(p.second.c_str(), ("Bindings for " + p.first + "::" + p.second + " namespace").c_str() ); + + //pybind11::class_>(M(""), "_encapsulated_data_"); + + bind_T60_const(M); + bind_T60_const_1(M); + +} + +// Source list file: /home/andriish/Projects/binder/test//T60_const.sources +// T60_const.cpp +// T60_const.cpp +// T60_const_1.cpp + +// Modules list file: /home/andriish/Projects/binder/test//T60_const.modules +// foo