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.
This commit is contained in:
Thomas Geymayer
2013-03-03 19:26:25 +01:00
parent 0bc8005100
commit f21127fd4a
7 changed files with 350 additions and 196 deletions

View File

@@ -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)

View File

@@ -30,6 +30,8 @@
#include <boost/function.hpp>
#include <boost/lambda/lambda.hpp>
#include <boost/mpl/has_xxx.hpp>
#include <boost/preprocessor/iteration/iterate.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/utility/enable_if.hpp>
#include <map>
@@ -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<class T>
T getArg(size_t index, const T& def = T()) const
typename from_nasal_ptr<T>::return_type
getArg(size_t index, const T& def = T()) const
{
if( index >= argc )
return def;
return from_nasal<T>(c, args[index]);
return (*from_nasal_ptr<T>::get())(c, args[index]);
}
/**
@@ -144,12 +157,13 @@ namespace nasal
* are to few arguments available.
*/
template<class T>
T requireArg(size_t index) const
typename from_nasal_ptr<T>::return_type
requireArg(size_t index) const
{
if( index >= argc )
naRuntimeError(c, "Missing required arg #%d", index);
return from_nasal<T>(c, args[index]);
return (*from_nasal_ptr<T>::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<MyClass> 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<naRef(naContext, raw_type&)> getter_t;
typedef boost::function<void(naContext, raw_type&, naRef)> setter_t;
typedef boost::function<naRef(raw_type&, const CallContext&)> method_t;
typedef boost::shared_ptr<method_t> method_ptr;
typedef boost::shared_ptr<internal::MethodHolder> 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<MethodHolder*>(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<std::string, member_t> MemberMap;
@@ -367,22 +431,24 @@ namespace nasal
member_t m;
if( getter )
{
typedef typename boost::call_traits<Ret>::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<Ret>::get(),
_1,
boost::bind(getter, _2)
);
}
if( setter )
{
typename boost::remove_const
< typename boost::remove_reference<Param>::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<Param>::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<MyClassPtr>::init("Test")
* .method<&MyClass::myMethod>("myMethod");
* .method("myMethod", &MyClass::myMethod);
* @endcode
*/
template<member_func_t func>
Ghost& method(const std::string& name)
Ghost& method(const std::string& name, const method_t& func)
{
_members[name].func = &MemberFunctionWrapper<func>::call;
_members[name].func.reset( new MethodHolder(func) );
return *this;
}
/**
* Invoke a method which returns a value and convert it to Nasal.
*/
template<class Ret>
static
typename boost::disable_if<boost::is_void<Ret>, naRef>::type
method_invoker
(
const boost::function<Ret (raw_type&, const CallContext&)>& func,
raw_type& obj,
const CallContext& ctx
)
{
typedef typename boost::call_traits<Ret>::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<class Ret>
static
typename boost::enable_if<boost::is_void<Ret>, naRef>::type
method_invoker
(
const boost::function<void (raw_type&, const CallContext&)>& 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<MyClassPtr>::init("Test")
* .method("doIt", &doIt);
* @endcode
*/
template<class Ret>
Ghost& method
@@ -516,67 +545,14 @@ namespace nasal
const boost::function<Ret (raw_type&, const CallContext&)>& func
)
{
// _members[name].func.reset
// (
new method_t( boost::bind(method_invoker<Ret>, func, _1, _2) );
// );
return *this;
return method(name, boost::bind(method_invoker<Ret>, func, _1, _2));
}
template<class Ret>
struct method_raw
{
typedef boost::function<Ret (raw_type&, const CallContext&)> type;
};
/**
* Bind member function as method callable from Nasal
*
* Does not really register method yet!!!
*/
template<class Ret>
Ghost& method( const std::string& name,
Ret (raw_type::*fn)() const )
{
return method<Ret>
(
name,
typename method_raw<Ret>::type(boost::bind(fn, _1))
);
}
/**
* 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<MyClassPtr>::init("Test")
* .method_func<&myMethod>("myMethod");
* @endcode
*/
template<free_func_t func>
Ghost& method_func(const std::string& name)
{
_members[name].func = &FreeFunctionWrapper<func>::call;
return *this;
}
#define BOOST_PP_ITERATION_LIMITS (0, 9)
#define BOOST_PP_FILENAME_1 <simgear/nasal/cppbind/detail/functor_templates.hxx>
#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<member_func_t func>
struct MemberFunctionWrapper
template<class Ret>
static
typename boost::disable_if<boost::is_void<Ret>, naRef>::type
method_invoker
(
const boost::function<Ret (raw_type&, const CallContext&)>& 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));
}
return (*to_nasal_ptr<Ret>::get())(ctx.c, func(obj, ctx));
};
/**
* 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.
* Invoke a method which returns void and "convert" it to nil.
*/
template<free_func_t func>
struct FreeFunctionWrapper
template<class Ret>
static
typename boost::enable_if<boost::is_void<Ret>, naRef>::type
method_invoker
(
const boost::function<Ret (raw_type&, const CallContext&)>& func,
raw_type& obj,
const CallContext& ctx
)
{
/**
* 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));
}
func(obj, ctx);
return naNil();
};
/**
* Extract argument by index from nasal::CallContext and convert to given
* type.
*/
template<class Arg>
static
typename boost::disable_if<
boost::is_same<Arg, const CallContext&>,
typename from_nasal_ptr<Arg>::return_type
>::type
arg_from_nasal(const CallContext& ctx, size_t index)
{
return ctx.requireArg<Arg>(index);
};
/**
* Specialization to pass through nasal::CallContext.
*/
template<class Arg>
static
typename boost::enable_if<
boost::is_same<Arg, const CallContext&>,
typename from_nasal_ptr<Arg>::return_type
>::type
arg_from_nasal(const CallContext& ctx, size_t)
{
return ctx;
};
typedef std::auto_ptr<Ghost> 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);
}

View File

@@ -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<BasePtr>::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<std::string>(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<BasePtr>::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<DerivedPtr>::init("DerivedPtr")
.bases<BasePtr>()
.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<DoubleDerivedPtr>::init("DoubleDerivedPtr")
.bases<DerivedPtr>();
Ghost<DoubleDerived2Ptr>::init("DoubleDerived2Ptr")
@@ -222,6 +217,12 @@ int main(int argc, char* argv[])
derived_obj.set("parents", parents2);
VERIFY( Ghost<BasePtr>::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<std::string>(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);

View File

@@ -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<Ret (raw_type& BOOST_PP_COMMA_IF(n)BOOST_PP_ENUM_PARAMS(n,A))>
/**
* 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<A##n>, _2, n)
return method<Ret>
(
name,
typename boost::function<Ret (raw_type&, const CallContext&)>
( 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<raw_type&, Type>::value
//|| boost::is_convertible<raw_type*, Type>::value
// TODO check how to do it with pointer...
));
return method<Ret>(name, SG_GHOST_FUNC_TYPE(fn));
}
#undef n
#undef SG_GHOST_TYPEDEF_FUNC_TYPE

View File

@@ -45,6 +45,24 @@ namespace nasal
return from_nasal_helper(c, ref, static_cast<T*>(0));
}
/**
* Get pointer to specific version of from_nasal, converting to a type
* compatible to Var.
*/
template<class Var>
struct from_nasal_ptr
{
typedef typename boost::remove_const
< typename boost::remove_reference<Var>::type
>::type return_type;
typedef return_type(*type)(naContext, naRef);
static type get()
{
return &from_nasal<return_type>;
}
};
} // namespace nasal
#endif /* SG_FROM_NASAL_HXX_ */

View File

@@ -50,7 +50,7 @@ namespace nasal
}
//----------------------------------------------------------------------------
naRef to_nasal(naContext c, naRef ref)
naRef to_nasal(naContext c, const naRef& ref)
{
return ref;
}

View File

@@ -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<class Var>
struct to_nasal_ptr
{
typedef typename boost::call_traits<Var>::param_type param_type;
typedef naRef(*type)(naContext, param_type);
static type get()
{
return static_cast<type>(&to_nasal);
}
};
//----------------------------------------------------------------------------
template<class Vec2>
typename boost::enable_if<is_vec2<Vec2>, naRef>::type