From: Thomas Geymayer Date: Sun, 3 Mar 2013 18:26:25 +0000 (+0100) Subject: cppbind: rework to allow binding nearly everything. X-Git-Url: https://git.mxchange.org/?a=commitdiff_plain;h=f21127fd4aa742e26d554cdc98911b6c7671021c;p=simgear.git cppbind: rework to allow binding nearly everything. It is now possible to register all types of member function and free functions as methods for nasal::Ghost objects. The return value and arguments are converte automatically to the required types. Also usage is simplified by removing replacing the old method and method_func with a single method function which only needs a name for the method and something callable. --- diff --git a/simgear/nasal/cppbind/CMakeLists.txt b/simgear/nasal/cppbind/CMakeLists.txt index 18507d8a..ed8272fd 100644 --- a/simgear/nasal/cppbind/CMakeLists.txt +++ b/simgear/nasal/cppbind/CMakeLists.txt @@ -10,6 +10,10 @@ set(HEADERS to_nasal.hxx ) +set(DETAIL_HEADERS + detail/functor_templates.hxx +) + set(SOURCES NasalHash.cxx NasalString.cxx @@ -18,6 +22,7 @@ set(SOURCES ) simgear_component(nasal/cppbind nasal/cppbind "${SOURCES}" "${HEADERS}") +simgear_component(nasal/cppbind/detail nasal/cppbind/detail "" "${DETAIL_HEADERS}") if(ENABLE_TESTS) diff --git a/simgear/nasal/cppbind/Ghost.hxx b/simgear/nasal/cppbind/Ghost.hxx index 88133e9d..a01e9d74 100644 --- a/simgear/nasal/cppbind/Ghost.hxx +++ b/simgear/nasal/cppbind/Ghost.hxx @@ -30,6 +30,8 @@ #include #include #include +#include +#include #include #include @@ -108,6 +110,16 @@ namespace nasal } }; + /** + * Hold callable method and convert to Nasal function if required. + */ + class MethodHolder + { + public: + virtual ~MethodHolder() {} + virtual naRef get_naRef(naContext c) = 0; + }; + BOOST_MPL_HAS_XXX_TRAIT_DEF(element_type) } @@ -131,12 +143,13 @@ namespace nasal * @param def Default value returned if too few arguments available */ template - T getArg(size_t index, const T& def = T()) const + typename from_nasal_ptr::return_type + getArg(size_t index, const T& def = T()) const { if( index >= argc ) return def; - return from_nasal(c, args[index]); + return (*from_nasal_ptr::get())(c, args[index]); } /** @@ -144,12 +157,13 @@ namespace nasal * are to few arguments available. */ template - T requireArg(size_t index) const + typename from_nasal_ptr::return_type + requireArg(size_t index) const { if( index >= argc ) naRuntimeError(c, "Missing required arg #%d", index); - return from_nasal(c, args[index]); + return (*from_nasal_ptr::get())(c, args[index]); } naContext c; @@ -168,10 +182,13 @@ namespace nasal * void setX(int x); * int getX() const; * - * naRef myMember(naContext c, int argc, naRef* args); + * int myMember(); + * void doSomethingElse(const nasal::CallContext& ctx); * } * typedef boost::shared_ptr MyClassPtr; * + * std::string myOtherFreeMember(int num); + * * void exposeClasses() * { * // Register a nasal ghost type for MyClass. This needs to be done only @@ -184,9 +201,12 @@ namespace nasal * .member("x_readonly", &MyClass::getX) * // It is also possible to expose writeonly members * .member("x_writeonly", &MyClass::setX) - * // Methods use a slightly different syntax - The pointer to the member - * // function has to be passed as template argument - * .method<&MyClass::myMember>("myMember"); + * // Methods can be nearly anything callable and accepting a reference + * // to an instance of the class type. (member functions, free functions + * // and anything else bindable using boost::function and boost::bind) + * .method("myMember", &MyClass::myMember) + * .method("doSomething", &MyClass::doSomethingElse) + * .method("other", &myOtherFreeMember); * } * @endcode */ @@ -204,7 +224,52 @@ namespace nasal typedef boost::function getter_t; typedef boost::function setter_t; typedef boost::function method_t; - typedef boost::shared_ptr method_ptr; + typedef boost::shared_ptr MethodHolderPtr; + + class MethodHolder: + public internal::MethodHolder + { + public: + MethodHolder(): + _naRef(naNil()) + {} + + explicit MethodHolder(const method_t& method): + _method(method), + _naRef(naNil()) + {} + + virtual naRef get_naRef(naContext c) + { + if( naIsNil(_naRef) ) + { + _naRef = naNewFunc(c, naNewCCodeU(c, &MethodHolder::call, this)); + naSave(c, _naRef); + } + return _naRef; + } + + protected: + method_t _method; + naRef _naRef; + + static naRef call( naContext c, + naRef me, + int argc, + naRef* args, + void* user_data ) + { + MethodHolder* holder = static_cast(user_data); + if( !holder ) + naRuntimeError(c, "invalid method holder!"); + + return holder->_method + ( + requireObject(c, me), + CallContext(c, argc, args) + ); + } + }; /** * A ghost member. Can consist either of getter and/or setter functions @@ -212,25 +277,24 @@ namespace nasal */ struct member_t { - member_t(): - func(0) + member_t() {} member_t( const getter_t& getter, const setter_t& setter, - naCFunction func = 0 ): + const MethodHolderPtr& func = MethodHolderPtr() ): getter( getter ), setter( setter ), func( func ) {} - member_t(naCFunction func): + explicit member_t(const MethodHolderPtr& func): func( func ) {} - getter_t getter; - setter_t setter; - naCFunction func; + getter_t getter; + setter_t setter; + MethodHolderPtr func; }; typedef std::map MemberMap; @@ -367,22 +431,24 @@ namespace nasal member_t m; if( getter ) { - typedef typename boost::call_traits::param_type param_type; - naRef (*to_nasal_)(naContext, param_type) = &nasal::to_nasal; - // Getter signature: naRef(naContext, raw_type&) - m.getter = boost::bind(to_nasal_, _1, boost::bind(getter, _2)); + m.getter = boost::bind + ( + to_nasal_ptr::get(), + _1, + boost::bind(getter, _2) + ); } if( setter ) { - typename boost::remove_const - < typename boost::remove_reference::type - >::type - (*from_nasal_)(naContext, naRef) = &nasal::from_nasal; - // Setter signature: void(naContext, raw_type&, naRef) - m.setter = boost::bind(setter, _2, boost::bind(from_nasal_, _1, _3)); + m.setter = boost::bind + ( + setter, + _2, + boost::bind(from_nasal_ptr::get(), _1, _3) + ); } return member(field, m.getter, m.setter); @@ -439,75 +505,38 @@ namespace nasal } /** - * Register a member function. - * - * @note Because only function pointers can be registered as Nasal - * functions it is needed to pass the function pointer as template - * argument. This allows us to create a separate instance of the - * MemberFunctionWrapper for each registered function and therefore - * provides us with the needed static functions to be passed on to - * Nasal. - * - * @tparam func Pointer to member function being registered. + * Register anything that accepts an object instance and a + * nasal::CallContext and returns naRef as method. * * @code{cpp} * class MyClass * { * public: - * naRef myMethod(naContext c, int argc, naRef* args); + * naRef myMethod(const nasal::CallContext& ctx); * } * * Ghost::init("Test") - * .method<&MyClass::myMethod>("myMethod"); + * .method("myMethod", &MyClass::myMethod); * @endcode */ - template - Ghost& method(const std::string& name) + Ghost& method(const std::string& name, const method_t& func) { - _members[name].func = &MemberFunctionWrapper::call; + _members[name].func.reset( new MethodHolder(func) ); return *this; } /** - * Invoke a method which returns a value and convert it to Nasal. - */ - template - static - typename boost::disable_if, naRef>::type - method_invoker - ( - const boost::function& func, - raw_type& obj, - const CallContext& ctx - ) - { - typedef typename boost::call_traits::param_type param_type; - naRef (*to_nasal_)(naContext, param_type) = &nasal::to_nasal; - - return to_nasal_(ctx.c, func(obj, ctx)); - }; - - /** - * Invoke a method which returns void and "convert" it to nil. - */ - template - static - typename boost::enable_if, naRef>::type - method_invoker - ( - const boost::function& func, - raw_type& obj, - const CallContext& ctx - ) - { - func(obj, ctx); - return naNil(); - }; - - /** - * Bind any callable entity as method callable from Nasal + * Register anything that accepts an object instance and a + * nasal::CallContext whith automatic conversion of the return type to + * Nasal. * - * Does not really register method yet!!! + * @code{cpp} + * class MyClass; + * void doIt(const MyClass& c, const nasal::CallContext& ctx); + * + * Ghost::init("Test") + * .method("doIt", &doIt); + * @endcode */ template Ghost& method @@ -516,67 +545,14 @@ namespace nasal const boost::function& func ) { -// _members[name].func.reset -// ( - new method_t( boost::bind(method_invoker, func, _1, _2) ); -// ); - return *this; - } - - template - struct method_raw - { - typedef boost::function type; - }; - - /** - * Bind member function as method callable from Nasal - * - * Does not really register method yet!!! - */ - template - Ghost& method( const std::string& name, - Ret (raw_type::*fn)() const ) - { - return method - ( - name, - typename method_raw::type(boost::bind(fn, _1)) - ); + return method(name, boost::bind(method_invoker, func, _1, _2)); } - /** - * Register a free function as member function. The object instance is - * passed as additional first argument. - * - * @tparam func Pointer to free function being registered. - * - * @note Due to a severe bug in Visual Studio it is not possible to create - * a specialization of #method for free function pointers and - * member function pointers at the same time. Do overcome this - * limitation we had to use a different name for this function. - * - * @code{cpp} - * class MyClass; - * naRef myMethod(MyClass& obj, naContext c, int argc, naRef* args); - * - * Ghost::init("Test") - * .method_func<&myMethod>("myMethod"); - * @endcode - */ - template - Ghost& method_func(const std::string& name) - { - _members[name].func = &FreeFunctionWrapper::call; - return *this; - } +#define BOOST_PP_ITERATION_LIMITS (0, 9) +#define BOOST_PP_FILENAME_1 +#include BOOST_PP_ITERATE() // TODO use variadic template when supporting C++11 - /** - * Create a Nasal instance of this ghost. - * - * @param c Active Nasal context - */ // TODO check if default constructor exists // static naRef create( naContext c ) // { @@ -768,44 +744,65 @@ namespace nasal } /** - * Wrapper class to enable registering pointers to member functions as - * Nasal function callbacks. We need to use the function pointer as - * template parameter to ensure every registered function gets a static - * function which can be passed to Nasal. + * Invoke a method which returns a value and convert it to Nasal. + */ + template + static + typename boost::disable_if, naRef>::type + method_invoker + ( + const boost::function& func, + raw_type& obj, + const CallContext& ctx + ) + { + return (*to_nasal_ptr::get())(ctx.c, func(obj, ctx)); + }; + + /** + * Invoke a method which returns void and "convert" it to nil. */ - template - struct MemberFunctionWrapper + template + static + typename boost::enable_if, naRef>::type + method_invoker + ( + const boost::function& func, + raw_type& obj, + const CallContext& ctx + ) { - /** - * Called from Nasal upon invocation of the according registered - * function. Forwards the call to the passed object instance. - */ - static naRef call(naContext c, naRef me, int argc, naRef* args) - { - return (requireObject(c, me).*func)(CallContext(c, argc, args)); - } + func(obj, ctx); + return naNil(); }; /** - * Wrapper class to enable registering pointers to free functions (only - * external linkage). We need to use the function pointer as template - * parameter to ensure every registered function gets a static function - * which can be passed to Nasal. Even though we just wrap another simple - * function pointer this intermediate step is need to be able to retrieve - * the object the function call belongs to and pass it along as argument. + * Extract argument by index from nasal::CallContext and convert to given + * type. */ - template - struct FreeFunctionWrapper + template + static + typename boost::disable_if< + boost::is_same, + typename from_nasal_ptr::return_type + >::type + arg_from_nasal(const CallContext& ctx, size_t index) { - /** - * Called from Nasal upon invocation of the according registered - * function. Forwards the call to the passed function pointer and passes - * the required parameters. - */ - static naRef call(naContext c, naRef me, int argc, naRef* args) - { - return func(requireObject(c, me), CallContext(c, argc, args)); - } + return ctx.requireArg(index); + }; + + /** + * Specialization to pass through nasal::CallContext. + */ + template + static + typename boost::enable_if< + boost::is_same, + typename from_nasal_ptr::return_type + >::type + arg_from_nasal(const CallContext& ctx, size_t) + { + return ctx; }; typedef std::auto_ptr GhostPtr; @@ -871,7 +868,7 @@ namespace nasal return 0; if( member->second.func ) - *out = nasal::to_nasal(c, member->second.func); + *out = member->second.func->get_naRef(c); else if( !member->second.getter.empty() ) *out = member->second.getter(c, *getRawPtr(g)); else @@ -891,8 +888,10 @@ namespace nasal if( member == getSingletonPtr()->_members.end() ) naRuntimeError(c, "ghost: No such member: %s", key.c_str()); - if( member->second.setter.empty() ) + else if( member->second.setter.empty() ) naRuntimeError(c, "ghost: Write protected member: %s", key.c_str()); + else if( member->second.func ) + naRuntimeError(c, "ghost: Write to function: %s", key.c_str()); member->second.setter(c, *getRawPtr(g), val); } diff --git a/simgear/nasal/cppbind/cppbind_test.cxx b/simgear/nasal/cppbind/cppbind_test.cxx index cd80244b..90bbf855 100644 --- a/simgear/nasal/cppbind/cppbind_test.cxx +++ b/simgear/nasal/cppbind/cppbind_test.cxx @@ -24,6 +24,8 @@ struct Base std::string getString() const { return ""; } void setString(const std::string&) {} void constVoidFunc() const {} + int test1Arg(const std::string& str) const { return str.length(); } + bool test2Args(const std::string& s, bool c) { return c && s.empty(); } std::string var; const std::string& getVar() const { return var; } @@ -32,6 +34,9 @@ struct Base void baseVoidFunc(Base& b) {} void baseConstVoidFunc(const Base& b) {} +int baseFunc2Args(Base& b, int x, const std::string& s) { return x + s.size(); } +std::string testPtr(Base& b) { return b.getString(); } +void baseFuncCallContext(const Base&, const nasal::CallContext&) {} struct Derived: public Base @@ -61,7 +66,7 @@ naRef to_nasal(naContext c, const BasePtr& base) return nasal::Ghost::create(c, base); } -naRef member(Derived&, const nasal::CallContext&) { return naNil(); } +naRef derivedFreeMember(Derived&, const nasal::CallContext&) { return naNil(); } naRef f_derivedGetX(naContext c, const Derived& d) { return nasal::to_nasal(c, d.getX()); @@ -126,41 +131,31 @@ int main(int argc, char* argv[]) Hash mod = hash.createHash("mod"); mod.set("parent", hash); - String string( to_nasal(c, "Test") ); - VERIFY( from_nasal(c, string.get_naRef()) == "Test" ); - VERIFY( string.c_str() == std::string("Test") ); - VERIFY( string.starts_with(string) ); - VERIFY( string.starts_with(String(c, "T")) ); - VERIFY( string.starts_with(String(c, "Te")) ); - VERIFY( string.starts_with(String(c, "Tes")) ); - VERIFY( string.starts_with(String(c, "Test")) ); - VERIFY( !string.starts_with(String(c, "Test1")) ); - VERIFY( !string.starts_with(String(c, "bb")) ); - VERIFY( !string.starts_with(String(c, "bbasdasdafasd")) ); - VERIFY( string.find('e') == 1 ); - VERIFY( string.find('9') == String::npos ); - VERIFY( string.find_first_of(String(c, "st")) == 2 ); - VERIFY( string.find_first_of(String(c, "st"), 3) == 3 ); - VERIFY( string.find_first_of(String(c, "xyz")) == String::npos ); - VERIFY( string.find_first_not_of(String(c, "Tst")) == 1 ); - VERIFY( string.find_first_not_of(String(c, "Tse"), 2) == 3 ); - VERIFY( string.find_first_not_of(String(c, "abc")) == 0 ); - VERIFY( string.find_first_not_of(String(c, "abc"), 20) == String::npos ); + //---------------------------------------------------------------------------- + // Test exposing classes to Nasal + //---------------------------------------------------------------------------- Ghost::init("BasePtr") - .method<&Base::member>("member") + .method("member", &Base::member) + .method("strlen", &Base::test1Arg) .member("str", &Base::getString, &Base::setString) .method("str_m", &Base::getString) .method("void", &Base::constVoidFunc) .member("var_r", &Base::getVar) .member("var_w", &Base::setVar) .member("var", &Base::getVar, &Base::setVar) - /*.method("void", &baseVoidFunc)*/; + .method("void", &baseVoidFunc) + .method("void_c", &baseConstVoidFunc) + .method("int2args", &baseFunc2Args) + .method("bool2args", &Base::test2Args) + .method("str_ptr", &testPtr); Ghost::init("DerivedPtr") .bases() .member("x", &Derived::getX, &Derived::setX) .member("x_alternate", &f_derivedGetX) - .method_func<&member>("free_member"); + .method("free_fn", &derivedFreeMember) + .method("free_member", &derivedFreeMember) + .method("baseDoIt", &baseFuncCallContext); Ghost::init("DoubleDerivedPtr") .bases(); Ghost::init("DoubleDerived2Ptr") @@ -222,6 +217,12 @@ int main(int argc, char* argv[]) derived_obj.set("parents", parents2); VERIFY( Ghost::fromNasal(c, derived_obj.get_naRef()) == d3 ); + // TODO actually do something with the ghosts... + + //---------------------------------------------------------------------------- + // Test nasal::CallContext + //---------------------------------------------------------------------------- + naRef args[] = { to_nasal(c, std::string("test-arg")) }; @@ -233,7 +234,30 @@ int main(int argc, char* argv[]) naRef args_vec = nasal::to_nasal(c, args); VERIFY( naIsVector(args_vec) ); - // TODO actually do something with the ghosts... + //---------------------------------------------------------------------------- + // Test nasal::String + //---------------------------------------------------------------------------- + + String string( to_nasal(c, "Test") ); + VERIFY( from_nasal(c, string.get_naRef()) == "Test" ); + VERIFY( string.c_str() == std::string("Test") ); + VERIFY( string.starts_with(string) ); + VERIFY( string.starts_with(String(c, "T")) ); + VERIFY( string.starts_with(String(c, "Te")) ); + VERIFY( string.starts_with(String(c, "Tes")) ); + VERIFY( string.starts_with(String(c, "Test")) ); + VERIFY( !string.starts_with(String(c, "Test1")) ); + VERIFY( !string.starts_with(String(c, "bb")) ); + VERIFY( !string.starts_with(String(c, "bbasdasdafasd")) ); + VERIFY( string.find('e') == 1 ); + VERIFY( string.find('9') == String::npos ); + VERIFY( string.find_first_of(String(c, "st")) == 2 ); + VERIFY( string.find_first_of(String(c, "st"), 3) == 3 ); + VERIFY( string.find_first_of(String(c, "xyz")) == String::npos ); + VERIFY( string.find_first_not_of(String(c, "Tst")) == 1 ); + VERIFY( string.find_first_not_of(String(c, "Tse"), 2) == 3 ); + VERIFY( string.find_first_not_of(String(c, "abc")) == 0 ); + VERIFY( string.find_first_not_of(String(c, "abc"), 20) == String::npos ); naFreeContext(c); diff --git a/simgear/nasal/cppbind/detail/functor_templates.hxx b/simgear/nasal/cppbind/detail/functor_templates.hxx new file mode 100644 index 00000000..51bb6c99 --- /dev/null +++ b/simgear/nasal/cppbind/detail/functor_templates.hxx @@ -0,0 +1,92 @@ +#ifndef SG_NASAL_GHOST_HXX_ +# error Nasal cppbind - do not include this file! +#endif + +#define n BOOST_PP_ITERATION() + +#define SG_GHOST_FUNC_TYPE\ + boost::function + + /** + * Bind any callable entity accepting an instance of raw_type and an arbitrary + * number of arguments as method. + */ + template< + class Ret + BOOST_PP_COMMA_IF(n) + BOOST_PP_ENUM_PARAMS(n, class A) + > + Ghost& method(const std::string& name, const SG_GHOST_FUNC_TYPE& func) + { +#define SG_GHOST_REQUIRE_ARG(z, n, dummy)\ + boost::bind(&Ghost::arg_from_nasal, _2, n) + + return method + ( + name, + typename boost::function + ( boost::bind( + func, + _1 + BOOST_PP_COMMA_IF(n) + BOOST_PP_ENUM(n, SG_GHOST_REQUIRE_ARG,) + )) + ); + +#undef SG_GHOST_REQUIRE_ARG + } + +#define SG_GHOST_MEM_FN(cv)\ + /**\ + * Bind a member function with an arbitrary number of arguments as method.\ + */\ + template<\ + class Ret\ + BOOST_PP_COMMA_IF(n)\ + BOOST_PP_ENUM_PARAMS(n, class A)\ + >\ + Ghost& method\ + (\ + const std::string& name,\ + Ret (raw_type::*fn)(BOOST_PP_ENUM_PARAMS(n,A)) cv\ + )\ + {\ + return method<\ + Ret\ + BOOST_PP_COMMA_IF(n)\ + BOOST_PP_ENUM_PARAMS(n,A)\ + >(name, SG_GHOST_FUNC_TYPE(fn));\ + } + + + SG_GHOST_MEM_FN() + SG_GHOST_MEM_FN(const) + +#undef SG_GHOST_MEM_FN + + /** + * Bind free function accepting an instance of raw_type and an arbitrary + * number of arguments as method. + */ + template< + class Ret, + class Type + BOOST_PP_COMMA_IF(n) + BOOST_PP_ENUM_PARAMS(n, class A) + > + Ghost& method + ( + const std::string& name, + Ret (*fn)(Type BOOST_PP_COMMA_IF(n) BOOST_PP_ENUM_PARAMS(n,A)) + ) + { + BOOST_STATIC_ASSERT + (( boost::is_convertible::value + //|| boost::is_convertible::value + // TODO check how to do it with pointer... + )); + return method(name, SG_GHOST_FUNC_TYPE(fn)); + } + +#undef n +#undef SG_GHOST_TYPEDEF_FUNC_TYPE diff --git a/simgear/nasal/cppbind/from_nasal.hxx b/simgear/nasal/cppbind/from_nasal.hxx index 37b63659..de152135 100644 --- a/simgear/nasal/cppbind/from_nasal.hxx +++ b/simgear/nasal/cppbind/from_nasal.hxx @@ -45,6 +45,24 @@ namespace nasal return from_nasal_helper(c, ref, static_cast(0)); } + /** + * Get pointer to specific version of from_nasal, converting to a type + * compatible to Var. + */ + template + struct from_nasal_ptr + { + typedef typename boost::remove_const + < typename boost::remove_reference::type + >::type return_type; + typedef return_type(*type)(naContext, naRef); + + static type get() + { + return &from_nasal; + } + }; + } // namespace nasal #endif /* SG_FROM_NASAL_HXX_ */ diff --git a/simgear/nasal/cppbind/to_nasal.cxx b/simgear/nasal/cppbind/to_nasal.cxx index 40b3cd4f..b5224ad2 100644 --- a/simgear/nasal/cppbind/to_nasal.cxx +++ b/simgear/nasal/cppbind/to_nasal.cxx @@ -50,7 +50,7 @@ namespace nasal } //---------------------------------------------------------------------------- - naRef to_nasal(naContext c, naRef ref) + naRef to_nasal(naContext c, const naRef& ref) { return ref; } diff --git a/simgear/nasal/cppbind/to_nasal.hxx b/simgear/nasal/cppbind/to_nasal.hxx index c6a80863..eeaf5a97 100644 --- a/simgear/nasal/cppbind/to_nasal.hxx +++ b/simgear/nasal/cppbind/to_nasal.hxx @@ -62,7 +62,7 @@ namespace nasal /** * Simple pass-through of naRef types to allow generic usage of to_nasal */ - naRef to_nasal(naContext c, naRef ref); + naRef to_nasal(naContext c, const naRef& ref); naRef to_nasal(naContext c, const SGPath& path); @@ -116,6 +116,22 @@ namespace nasal return ret; } + /** + * Wrapper to get pointer to specific version of to_nasal applicable to given + * type. + */ + template + struct to_nasal_ptr + { + typedef typename boost::call_traits::param_type param_type; + typedef naRef(*type)(naContext, param_type); + + static type get() + { + return static_cast(&to_nasal); + } + }; + //---------------------------------------------------------------------------- template typename boost::enable_if, naRef>::type