summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--indra/llcommon/CMakeLists.txt1
-rw-r--r--indra/llcommon/apply.cpp29
-rw-r--r--indra/llcommon/apply.h100
-rw-r--r--indra/llcommon/lleventdispatcher.cpp224
-rw-r--r--indra/llcommon/lleventdispatcher.h255
-rw-r--r--indra/llcommon/llptrto.h88
-rw-r--r--indra/llcommon/llsdutil.cpp35
-rw-r--r--indra/llcommon/llsdutil.h49
-rw-r--r--indra/llcommon/tests/lleventdispatcher_test.cpp141
9 files changed, 543 insertions, 379 deletions
diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt
index 941d2d7baf..33e8301e12 100644
--- a/indra/llcommon/CMakeLists.txt
+++ b/indra/llcommon/CMakeLists.txt
@@ -16,6 +16,7 @@ include(Tracy)
set(llcommon_SOURCE_FILES
+ apply.cpp
indra_constants.cpp
lazyeventapi.cpp
llallocator.cpp
diff --git a/indra/llcommon/apply.cpp b/indra/llcommon/apply.cpp
new file mode 100644
index 0000000000..417e23d3b4
--- /dev/null
+++ b/indra/llcommon/apply.cpp
@@ -0,0 +1,29 @@
+/**
+ * @file apply.cpp
+ * @author Nat Goodspeed
+ * @date 2022-12-21
+ * @brief Implementation for apply.
+ *
+ * $LicenseInfo:firstyear=2022&license=viewerlgpl$
+ * Copyright (c) 2022, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+// Precompiled header
+#include "linden_common.h"
+// associated header
+#include "apply.h"
+// STL headers
+// std headers
+// external library headers
+// other Linden headers
+#include "stringize.h"
+
+void LL::apply_validate_size(size_t size, size_t arity)
+{
+ if (size != arity)
+ {
+ LLTHROW(apply_error(stringize("LL::apply(func(", arity, " args), "
+ "std::vector(", size, " elements))")));
+ }
+}
diff --git a/indra/llcommon/apply.h b/indra/llcommon/apply.h
index 9f4c268895..357887dfd7 100644
--- a/indra/llcommon/apply.h
+++ b/indra/llcommon/apply.h
@@ -12,9 +12,11 @@
#if ! defined(LL_APPLY_H)
#define LL_APPLY_H
+#include "llexception.h"
#include <boost/type_traits/function_traits.hpp>
-#include <cassert>
+#include <functional> // std::mem_fn()
#include <tuple>
+#include <type_traits> // std::is_member_pointer
namespace LL
{
@@ -56,9 +58,39 @@ namespace LL
(ARGS))
/*****************************************************************************
-* apply(function, tuple)
+* invoke()
*****************************************************************************/
-#if __cplusplus >= 201703L
+#if __cpp_lib_invoke >= 201411L
+
+// C++17 implementation
+using std::invoke;
+
+#else // no std::invoke
+
+// Use invoke() to handle pointer-to-method:
+// derived from https://stackoverflow.com/a/38288251
+template<typename Fn, typename... Args,
+ typename std::enable_if<std::is_member_pointer<typename std::decay<Fn>::type>::value,
+ int>::type = 0 >
+auto invoke(Fn&& f, Args&&... args)
+{
+ return std::mem_fn(f)(std::forward<Args>(args)...);
+}
+
+template<typename Fn, typename... Args,
+ typename std::enable_if<!std::is_member_pointer<typename std::decay<Fn>::type>::value,
+ int>::type = 0 >
+auto invoke(Fn&& f, Args&&... args)
+{
+ return std::forward<Fn>(f)(std::forward<Args>(args)...);
+}
+
+#endif // no std::invoke
+
+/*****************************************************************************
+* apply(function, tuple); apply(function, array)
+*****************************************************************************/
+#if __cpp_lib_apply >= 201603L
// C++17 implementation
using std::apply;
@@ -71,7 +103,7 @@ template <typename CALLABLE, typename... ARGS, std::size_t... I>
auto apply_impl(CALLABLE&& func, const std::tuple<ARGS...>& args, std::index_sequence<I...>)
{
// call func(unpacked args)
- return std::forward<CALLABLE>(func)(std::move(std::get<I>(args))...);
+ return invoke(std::forward<CALLABLE>(func), std::get<I>(args)...);
}
template <typename CALLABLE, typename... ARGS>
@@ -85,11 +117,6 @@ auto apply(CALLABLE&& func, const std::tuple<ARGS...>& args)
std::index_sequence_for<ARGS...>{});
}
-#endif // C++14
-
-/*****************************************************************************
-* apply(function, std::array)
-*****************************************************************************/
// per https://stackoverflow.com/a/57510428/5533635
template <typename CALLABLE, typename T, size_t SIZE>
auto apply(CALLABLE&& func, const std::array<T, SIZE>& args)
@@ -97,6 +124,50 @@ auto apply(CALLABLE&& func, const std::array<T, SIZE>& args)
return apply(std::forward<CALLABLE>(func), std::tuple_cat(args));
}
+#endif // C++14
+
+/*****************************************************************************
+* bind_front()
+*****************************************************************************/
+// To invoke a non-static member function with a tuple, you need a callable
+// that binds your member function with an instance pointer or reference.
+// std::bind_front() is perfect: std::bind_front(&cls::method, instance).
+// Unfortunately bind_front() only enters the standard library in C++20.
+#if __cpp_lib_bind_front >= 201907L
+
+// C++20 implementation
+using std::bind_front;
+
+#else // no std::bind_front()
+
+template<typename Fn, typename... Args,
+ typename std::enable_if<!std::is_member_pointer<typename std::decay<Fn>::type>::value,
+ int>::type = 0 >
+auto bind_front(Fn&& f, Args&&... args)
+{
+ // Don't use perfect forwarding for f or args: we must bind them for later.
+ return [f, pfx_args=std::make_tuple(args...)]
+ (auto&&... sfx_args)
+ {
+ // Use perfect forwarding for sfx_args because we use them as soon as
+ // we receive them.
+ return apply(
+ f,
+ std::tuple_cat(pfx_args,
+ std::make_tuple(std::forward<decltype(sfx_args)>(sfx_args)...)));
+ };
+}
+
+template<typename Fn, typename... Args,
+ typename std::enable_if<std::is_member_pointer<typename std::decay<Fn>::type>::value,
+ int>::type = 0 >
+auto bind_front(Fn&& f, Args&&... args)
+{
+ return bind_front(std::mem_fn(std::forward<Fn>(f)), std::forward<Args>(args)...);
+}
+
+#endif // C++20 with std::bind_front()
+
/*****************************************************************************
* apply(function, std::vector)
*****************************************************************************/
@@ -108,6 +179,15 @@ auto apply_impl(CALLABLE&& func, const std::vector<T>& args, std::index_sequence
std::make_tuple(args[I]...));
}
+// produce suitable error if apply(func, vector) is the wrong size for func()
+void apply_validate_size(size_t size, size_t arity);
+
+/// possible exception from apply() validation
+struct apply_error: public LLException
+{
+ apply_error(const std::string& what): LLException(what) {}
+};
+
/**
* apply(function, std::vector) goes beyond C++17 std::apply(). For this case
* @a function @emph cannot be variadic: the compiler must know at compile
@@ -117,7 +197,7 @@ template <typename CALLABLE, typename T>
auto apply(CALLABLE&& func, const std::vector<T>& args)
{
constexpr auto arity = boost::function_traits<CALLABLE>::arity;
- assert(args.size() == arity);
+ apply_validate_size(args.size(), arity);
return apply_impl(std::forward<CALLABLE>(func),
args,
std::make_index_sequence<arity>());
diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp
index 3e45601429..e7e73125a7 100644
--- a/indra/llcommon/lleventdispatcher.cpp
+++ b/indra/llcommon/lleventdispatcher.cpp
@@ -50,69 +50,14 @@
*****************************************************************************/
struct DispatchError: public LLException
{
- DispatchError(const std::string& what): LLException(what) {}
+ // template constructor involving strings passes all arguments to
+ // stringize() to construct LLException's what() string
+ template <typename... ARGS>
+ DispatchError(const std::string& arg0, ARGS&&... args):
+ LLException(stringize(arg0, std::forward<ARGS>(args)...)) {}
};
/*****************************************************************************
-* LLSDArgsSource
-*****************************************************************************/
-/**
- * Store an LLSD array, producing its elements one at a time. It is an error
- * if the consumer requests more elements than the array contains.
- */
-class LL_COMMON_API LLSDArgsSource
-{
-public:
- LLSDArgsSource(const std::string function, const LLSD& args);
- ~LLSDArgsSource();
-
- LLSD next();
-
- void done() const;
-
-private:
- std::string _function;
- LLSD _args;
- LLSD::Integer _index;
-};
-
-LLSDArgsSource::LLSDArgsSource(const std::string function, const LLSD& args):
- _function(function),
- _args(args),
- _index(0)
-{
- if (! (_args.isUndefined() || _args.isArray()))
- {
- LLTHROW(DispatchError(stringize(_function, " needs an args array instead of ", _args)));
- }
-}
-
-LLSDArgsSource::~LLSDArgsSource()
-{
- done();
-}
-
-LLSD LLSDArgsSource::next()
-{
- if (_index >= _args.size())
- {
- LLTHROW(DispatchError(stringize(_function, " requires more arguments than the ",
- _args.size(), " provided: ", _args)));
- }
- return _args[_index++];
-}
-
-void LLSDArgsSource::done() const
-{
- if (_index < _args.size())
- {
- LL_WARNS("LLSDArgsSource") << _function << " only consumed " << _index
- << " of the " << _args.size() << " arguments provided: "
- << _args << LL_ENDL;
- }
-}
-
-/*****************************************************************************
* LLSDArgsMapper
*****************************************************************************/
/**
@@ -204,7 +149,7 @@ LLSDArgsMapper::LLSDArgsMapper(const std::string& function,
{
if (! (_names.isUndefined() || _names.isArray()))
{
- LLTHROW(DispatchError(stringize(function, " names must be an array, not ", names)));
+ LLTHROW(DispatchError(function, " names must be an array, not ", names));
}
auto nparams(_names.size());
// From _names generate _indexes.
@@ -227,8 +172,8 @@ LLSDArgsMapper::LLSDArgsMapper(const std::string& function,
// defaults is a (possibly empty) array. Right-align it with names.
if (ndefaults > nparams)
{
- LLTHROW(DispatchError(stringize(function, " names array ", names,
- " shorter than defaults array ", defaults)));
+ LLTHROW(DispatchError(function, " names array ", names,
+ " shorter than defaults array ", defaults));
}
// Offset by which we slide defaults array right to right-align with
@@ -265,14 +210,14 @@ LLSDArgsMapper::LLSDArgsMapper(const std::string& function,
}
if (bogus.size())
{
- LLTHROW(DispatchError(stringize(function, " defaults specified for nonexistent params ",
- formatlist(bogus))));
+ LLTHROW(DispatchError(function, " defaults specified for nonexistent params ",
+ formatlist(bogus)));
}
}
else
{
- LLTHROW(DispatchError(stringize(function, " defaults must be a map or an array, not ",
- defaults)));
+ LLTHROW(DispatchError(function, " defaults must be a map or an array, not ",
+ defaults));
}
}
@@ -280,8 +225,8 @@ LLSD LLSDArgsMapper::map(const LLSD& argsmap) const
{
if (! (argsmap.isUndefined() || argsmap.isMap() || argsmap.isArray()))
{
- LLTHROW(DispatchError(stringize(_function, " map() needs a map or array, not ",
- argsmap)));
+ LLTHROW(DispatchError(_function, " map() needs a map or array, not ",
+ argsmap));
}
// Initialize the args array. Indexing a non-const LLSD array grows it
// to appropriate size, but we don't want to resize this one on each
@@ -378,8 +323,8 @@ LLSD LLSDArgsMapper::map(const LLSD& argsmap) const
// by argsmap, that's a problem.
if (unfilled.size())
{
- LLTHROW(DispatchError(stringize(_function, " missing required arguments ",
- formatlist(unfilled), " from ", argsmap)));
+ LLTHROW(DispatchError(_function, " missing required arguments ",
+ formatlist(unfilled), " from ", argsmap));
}
// done
@@ -399,6 +344,9 @@ std::string LLSDArgsMapper::formatlist(const LLSD& list)
return out.str();
}
+/*****************************************************************************
+* LLEventDispatcher
+*****************************************************************************/
LLEventDispatcher::LLEventDispatcher(const std::string& desc, const std::string& key):
mDesc(desc),
mKey(key)
@@ -409,6 +357,10 @@ LLEventDispatcher::~LLEventDispatcher()
{
}
+LLEventDispatcher::DispatchEntry::DispatchEntry(const std::string& desc):
+ mDesc(desc)
+{}
+
/**
* DispatchEntry subclass used for callables accepting(const LLSD&)
*/
@@ -423,16 +375,17 @@ struct LLEventDispatcher::LLSDDispatchEntry: public LLEventDispatcher::DispatchE
Callable mFunc;
LLSD mRequired;
- virtual void call(const std::string& desc, const LLSD& event) const
+ virtual LLSD call(const std::string& desc, const LLSD& event) const
{
// Validate the syntax of the event itself.
std::string mismatch(llsd_matches(mRequired, event));
if (! mismatch.empty())
{
- LLTHROW(DispatchError(stringize(desc, ": bad request: ", mismatch)));
+ LLTHROW(DispatchError(desc, ": bad request: ", mismatch));
}
// Event syntax looks good, go for it!
mFunc(event);
+ return {};
}
virtual LLSD addMetadata(LLSD meta) const
@@ -455,10 +408,9 @@ struct LLEventDispatcher::ParamsDispatchEntry: public LLEventDispatcher::Dispatc
invoker_function mInvoker;
- virtual void call(const std::string& desc, const LLSD& event) const
+ virtual LLSD call(const std::string&, const LLSD& event) const
{
- LLSDArgsSource src(desc, event);
- mInvoker(boost::bind(&LLSDArgsSource::next, boost::ref(src)));
+ return mInvoker(event);
}
};
@@ -541,11 +493,11 @@ struct LLEventDispatcher::MapParamsDispatchEntry: public LLEventDispatcher::Para
LLSD mRequired;
LLSD mOptional;
- virtual void call(const std::string& desc, const LLSD& event) const
+ virtual LLSD call(const std::string& desc, const LLSD& event) const
{
// Just convert from LLSD::Map to LLSD::Array using mMapper, then pass
// to base-class call() method.
- ParamsDispatchEntry::call(desc, mMapper.map(event));
+ return ParamsDispatchEntry::call(desc, mMapper.map(event));
}
virtual LLSD addMetadata(LLSD meta) const
@@ -617,12 +569,18 @@ void LLEventDispatcher::operator()(const LLSD& event) const
void LLEventDispatcher::callFail(const LLSD& event, const std::string& msg) const
{
+ // pass back a response that includes an "error" key with the message.
+ reply(llsd::map("error", msg), event);
+}
+
+void LLEventDispatcher::reply(const LLSD& response, const LLSD& event) const
+{
static LLSD::String key{ "reply" };
if (event.has(key))
{
- // Oh good, the incoming event specifies a reply pump -- pass back a
- // response that includes an "error" key with the message.
- sendReply(llsd::map("error", msg), event, key);
+ // Oh good, the incoming event specifies a reply pump -- pass back
+ // our response.
+ sendReply(response, event, key);
}
}
@@ -631,17 +589,30 @@ bool LLEventDispatcher::try_call(const LLSD& event) const
return try_call_log(mKey, event[mKey], event).empty();
}
+/*==========================================================================*|
+ TODO:
+
+* When mInvoker returns result.isDefined(), sendReply(llsd::map("data", result))
+* When try_call finds name.isArray(), construct response array from
+ dispatching each call, sendReply() as above
+* When try_call finds name.isMap(), construct response map from dispatching
+ each call, sendReply() as above -- note, caller can't care about order
+* Possible future transactional behavior: look up all names before calling any
+
+|*==========================================================================*/
bool LLEventDispatcher::try_call(const std::string& name, const LLSD& event) const
{
return try_call_log(std::string(), name, event).empty();
}
-std::string LLEventDispatcher::try_call_log(const std::string& key, const std::string& name,
+std::string LLEventDispatcher::try_call_log(const std::string& key, const LLSD& name,
const LLSD& event) const
{
std::string error{ try_call(key, name, event) };
if (! error.empty())
{
+ // If we're a subclass of LLEventDispatcher, e.g. LLEventAPI, report that.
+ error = stringize(LLError::Log::classname(this), "(", mDesc, "): ", error);
LL_WARNS("LLEventDispatcher") << error << LL_ENDL;
}
return error;
@@ -649,33 +620,100 @@ std::string LLEventDispatcher::try_call_log(const std::string& key, const std::s
// This internal method returns empty string if the call succeeded, else
// non-empty error message.
-std::string LLEventDispatcher::try_call(const std::string& key, const std::string& name,
+std::string LLEventDispatcher::try_call(const std::string& key, const LLSD& name,
const LLSD& event) const
{
+ if (name.isUndefined())
+ {
+ if (key.empty())
+ {
+ return "attempting to call with no name";
+ }
+ else
+ {
+ return stringize("no ", key);
+ }
+ }
+ else if (name.isArray())
+ {
+ return stringize(key, " array dispatch ", name, " not yet implemented");
+ }
+ else if (name.isMap())
+ {
+ return stringize(key, " map dispatch ", name, " not yet implemented");
+ }
+ else if (! name.isString())
+ {
+ return stringize(key, " bad type ", LLSD::typeString(name.type()), ' ', name,
+ " -- function names are String");
+ }
+ else // name is an LLSD::String
+ {
+ auto success{ try_call_one(key, name, event) };
+ // pretend to unpack
+ std::string& error{ success.first };
+ LLSD& result{ success.second };
+ // did try_call_one() report an error?
+ if (! error.empty())
+ {
+ // if we have a reply key, respond to invoker
+ reply(llsd::map("error", error), event);
+ // now tell caller
+ return error;
+ }
+ // try_call_one() succeeded in calling the target function --
+ // should we reply to invoker?
+ if (result.isUndefined())
+ {
+ // We would get result.isUndefined() if the target function has
+ // void return. In any case, even if the target function returns
+ // LLSD, isUndefined() means "don't bother sending response."
+ return {};
+ }
+ // result.isDefined(): the target function returned something.
+ // Respond to invoker if we have a "reply" key.
+ if (! result.isMap())
+ {
+ // wrap result in a map to play well with sendReply()
+ result = llsd::map("data", result);
+ }
+ reply(result, event);
+ return {};
+ }
+}
+
+std::pair<std::string, LLSD>
+LLEventDispatcher::try_call_one(const std::string& key, const std::string& name,
+ const LLSD& event) const
+{
DispatchMap::const_iterator found = mDispatch.find(name);
if (found == mDispatch.end())
{
if (key.empty())
{
- return stringize("LLEventDispatcher(", mDesc, "): '", name, "' not found");
+ return { stringize("'", name, "' not found"), {} };
}
else
{
- return stringize("LLEventDispatcher(", mDesc, "): bad ", key, " value '", name, "'");
+ return { stringize("bad ", key, " value '", name, "'"), {} };
}
}
try
{
// Found the name, so it's plausible to even attempt the call.
- found->second->call(stringize("LLEventDispatcher(", mDesc, ") calling '", name, "'"),
- event);
+ return { {}, found->second->call(stringize("calling '", name, "'"), event) };
}
catch (const DispatchError& err)
{
- return err.what();
+ // trouble preparing arguments
+ return { err.what(), {} };
+ }
+ catch (const LL::apply_error& err)
+ {
+ // could also hit runtime errors with LL::apply()
+ return { err.what(), {} };
}
- return {}; // tell caller we were able to call
}
LLSD LLEventDispatcher::getMetadata(const std::string& name) const
@@ -691,6 +729,9 @@ LLSD LLEventDispatcher::getMetadata(const std::string& name) const
return found->second->addMetadata(meta);
}
+/*****************************************************************************
+* LLDispatchListener
+*****************************************************************************/
LLDispatchListener::LLDispatchListener(const std::string& pumpname, const std::string& key):
LLEventDispatcher(pumpname, key),
// Do NOT tweak the passed pumpname. In practice, when someone
@@ -712,8 +753,3 @@ bool LLDispatchListener::process(const LLSD& event)
(*this)(event);
return false;
}
-
-LLEventDispatcher::DispatchEntry::DispatchEntry(const std::string& desc):
- mDesc(desc)
-{}
-
diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h
index 09b786b69e..cebce618df 100644
--- a/indra/llcommon/lleventdispatcher.h
+++ b/indra/llcommon/lleventdispatcher.h
@@ -27,54 +27,23 @@
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
- *
- * The invoker machinery that constructs a boost::fusion argument list for use
- * with boost::fusion::invoke() is derived from
- * http://www.boost.org/doc/libs/1_45_0/libs/function_types/example/interpreter.hpp
- * whose license information is copied below:
- *
- * "(C) Copyright Tobias Schwinger
- *
- * Use modification and distribution are subject to the boost Software License,
- * Version 1.0. (See http://www.boost.org/LICENSE_1_0.txt)."
*/
#if ! defined(LL_LLEVENTDISPATCHER_H)
#define LL_LLEVENTDISPATCHER_H
-// nil is too generic a term to be allowed to be a global macro. In
-// particular, boost::fusion defines a 'class nil' (properly encapsulated in a
-// namespace) that a global 'nil' macro breaks badly.
-#if defined(nil)
-// Capture the value of the macro 'nil', hoping int is an appropriate type.
-static const auto nil_(nil);
-// Now forget the macro.
-#undef nil
-// Finally, reintroduce 'nil' as a properly-scoped alias for the previously-
-// defined const 'nil_'. Make it static since otherwise it produces duplicate-
-// symbol link errors later.
-static const auto& nil(nil_);
-#endif
-
-#include <boost/bind.hpp>
#include <boost/iterator/transform_iterator.hpp>
+#include <boost/function_types/is_member_function_pointer.hpp>
#include <boost/function_types/is_nonmember_callable_builtin.hpp>
-#include <boost/function_types/parameter_types.hpp>
#include <boost/function_types/function_arity.hpp>
-#include <boost/type_traits/remove_cv.hpp>
-#include <boost/type_traits/remove_reference.hpp>
-#include <boost/fusion/include/push_back.hpp>
-#include <boost/fusion/include/cons.hpp>
-#include <boost/fusion/include/invoke.hpp>
-#include <boost/mpl/begin.hpp>
-#include <boost/mpl/end.hpp>
-#include <boost/mpl/next.hpp>
-#include <boost/mpl/deref.hpp>
+#include <boost/function_types/result_type.hpp>
#include <functional> // std::function
#include <memory> // std::unique_ptr
#include <string>
#include <typeinfo>
+#include <type_traits>
#include "llevents.h"
+#include "llptrto.h"
#include "llsdutil.h"
class LLSD;
@@ -94,8 +63,7 @@ public:
/// @name Register functions accepting(const LLSD&)
//@{
- /// Accept any C++ callable with the right signature, typically a
- /// boost::bind() expression
+ /// Accept any C++ callable with the right signature
typedef std::function<void(const LLSD&)> Callable;
/**
@@ -126,7 +94,7 @@ public:
/**
* Special case: a subclass of this class can pass an unbound member
* function pointer (of an LLEventDispatcher subclass) without explicitly
- * specifying the <tt>boost::bind()</tt> expression. The passed @a method
+ * specifying a <tt>std::bind()</tt> expression. The passed @a method
* accepts a single LLSD value, presumably containing other parameters.
*/
template <class CLASS>
@@ -158,10 +126,6 @@ public:
* Register a free function with arbitrary parameters. (This also works
* for static class methods.)
*
- * @note This supports functions with up to about 6 parameters -- after
- * that you start getting dismaying compile errors in which
- * boost::fusion::joint_view is mentioned a surprising number of times.
- *
* When calling this name, pass an LLSD::Array. Each entry in turn will be
* converted to the corresponding parameter type using LLSDParam.
*/
@@ -175,10 +139,6 @@ public:
/**
* Register a nonstatic class method with arbitrary parameters.
*
- * @note This supports functions with up to about 6 parameters -- after
- * that you start getting dismaying compile errors in which
- * boost::fusion::joint_view is mentioned a surprising number of times.
- *
* To cover cases such as a method on an LLSingleton we don't yet want to
* instantiate, instead of directly storing an instance pointer, accept a
* nullary callable returning a pointer/reference to the desired class
@@ -201,10 +161,6 @@ public:
* Register a free function with arbitrary parameters. (This also works
* for static class methods.)
*
- * @note This supports functions with up to about 6 parameters -- after
- * that you start getting dismaying compile errors in which
- * boost::fusion::joint_view is mentioned a surprising number of times.
- *
* Pass an LLSD::Array of parameter names, and optionally another
* LLSD::Array of default parameter values, a la LLSDArgsMapper.
*
@@ -222,10 +178,6 @@ public:
/**
* Register a nonstatic class method with arbitrary parameters.
*
- * @note This supports functions with up to about 6 parameters -- after
- * that you start getting dismaying compile errors in which
- * boost::fusion::joint_view is mentioned a surprising number of times.
- *
* To cover cases such as a method on an LLSingleton we don't yet want to
* instantiate, instead of directly storing an instance pointer, accept a
* nullary callable returning a pointer/reference to the desired class
@@ -290,7 +242,7 @@ private:
std::string mDesc;
- virtual void call(const std::string& desc, const LLSD& event) const = 0;
+ virtual LLSD call(const std::string& desc, const LLSD& event) const = 0;
virtual LLSD addMetadata(LLSD) const = 0;
};
typedef std::map<std::string, std::unique_ptr<DispatchEntry> > DispatchMap;
@@ -322,17 +274,28 @@ private:
void addMethod(const std::string& name, const std::string& desc,
const METHOD& method, const LLSD& required)
{
- CLASS* downcast = static_cast<CLASS*>(this);
- add(name, desc, boost::bind(method, downcast, _1), required);
+ CLASS* downcast = dynamic_cast<CLASS*>(this);
+ if (! downcast)
+ {
+ addFail(name, typeid(CLASS).name());
+ }
+ else
+ {
+ add(name, desc, std::bind(method, downcast, std::placeholders::_1), required);
+ }
}
void addFail(const std::string& name, const std::string& classname) const;
- std::string try_call_log(const std::string& key, const std::string& name,
+ std::string try_call_log(const std::string& key, const LLSD& name,
const LLSD& event) const;
- std::string try_call(const std::string& key, const std::string& name,
+ std::string try_call(const std::string& key, const LLSD& name,
const LLSD& event) const;
+ // returns either (empty string, LLSD) or (error message, isUndefined)
+ std::pair<std::string, LLSD>
+ try_call_one(const std::string& key, const std::string& name, const LLSD& event) const;
// Implement "it is an error" semantics for attempted call operations: if
// the incoming event includes a "reply" key, log and send an error reply.
void callFail(const LLSD& event, const std::string& msg) const;
+ void reply(const LLSD& response, const LLSD& event) const;
std::string mDesc, mKey;
DispatchMap mDispatch;
@@ -347,20 +310,8 @@ private:
struct ArrayParamsDispatchEntry;
struct MapParamsDispatchEntry;
- // Step 2 of parameter analysis. Instantiating invoker<some_function_type>
- // implicitly sets its From and To parameters to the (compile time) begin
- // and end iterators over that function's parameter types.
- template< typename Function
- , class From = typename boost::mpl::begin< boost::function_types::parameter_types<Function> >::type
- , class To = typename boost::mpl::end< boost::function_types::parameter_types<Function> >::type
- >
- struct invoker;
-
- // deliver LLSD arguments one at a time
- typedef std::function<LLSD()> args_source;
- // obtain args from an args_source to build param list and call target
- // function
- typedef std::function<void(const args_source&)> invoker_function;
+ // call target function with args from LLSD array
+ typedef std::function<LLSD(const LLSD&)> invoker_function;
template <typename Function>
invoker_function make_invoker(Function f);
@@ -375,92 +326,17 @@ private:
const invoker_function& invoker,
const LLSD& params,
const LLSD& defaults);
+ template <typename RETURNTYPE>
+ struct ReturnLLSD;
};
/*****************************************************************************
* LLEventDispatcher template implementation details
*****************************************************************************/
-// Step 3 of parameter analysis, the recursive case.
-template<typename Function, class From, class To>
-struct LLEventDispatcher::invoker
-{
- template<typename T>
- struct remove_cv_ref
- : boost::remove_cv< typename boost::remove_reference<T>::type >
- { };
-
- // apply() accepts an arbitrary boost::fusion sequence as args. It
- // examines the next parameter type in the parameter-types sequence
- // bounded by From and To, obtains the next LLSD object from the passed
- // args_source and constructs an LLSDParam of appropriate type to try
- // to convert the value. It then recurs with the next parameter-types
- // iterator, passing the args sequence thus far.
- template<typename Args>
- static inline
- void apply(Function func, const args_source& argsrc, Args const & args)
- {
- typedef typename boost::mpl::deref<From>::type arg_type;
- typedef typename boost::mpl::next<From>::type next_iter_type;
- typedef typename remove_cv_ref<arg_type>::type plain_arg_type;
-
- invoker<Function, next_iter_type, To>::apply
- ( func, argsrc, boost::fusion::push_back(args, LLSDParam<plain_arg_type>(argsrc())));
- }
-
- // Special treatment for instance (first) parameter of a non-static member
- // function. Accept the instance-getter callable, calling that to produce
- // the first args value. Since we know we're at the top of the recursion
- // chain, we need not also require a partial args sequence from our caller.
- template <typename InstanceGetter>
- static inline
- void method_apply(Function func, const args_source& argsrc, const InstanceGetter& getter)
- {
- typedef typename boost::mpl::next<From>::type next_iter_type;
-
- // Instead of grabbing the first item from argsrc and making an
- // LLSDParam of it, call getter() and pass that as the instance param.
- invoker<Function, next_iter_type, To>::apply
- ( func, argsrc, boost::fusion::push_back(boost::fusion::nil(), bindable(getter())));
- }
-
- template <typename T>
- static inline
- auto bindable(T&& value,
- typename std::enable_if<std::is_pointer<T>::value, bool>::type=true)
- {
- // if passed a pointer, just return that pointer
- return std::forward<T>(value);
- }
-
- template <typename T>
- static inline
- auto bindable(T&& value,
- typename std::enable_if<! std::is_pointer<T>::value, bool>::type=true)
- {
- // if passed a reference, wrap it for binding
- return std::ref(std::forward<T>(value));
- }
-};
-
-// Step 4 of parameter analysis, the leaf case. When the general
-// invoker<Function, From, To> logic has advanced From until it matches To,
-// the compiler will pick this template specialization.
-template<typename Function, class To>
-struct LLEventDispatcher::invoker<Function,To,To>
-{
- // the argument list is complete, now call the function
- template<typename Args>
- static inline
- void apply(Function func, const args_source&, Args const & args)
- {
- boost::fusion::invoke(func, args);
- }
-};
-
template<typename Function, typename>
void LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f)
{
- // Construct an invoker_function, a callable accepting const args_source&.
+ // Construct an invoker_function, a callable accepting const LLSD&.
// Add to DispatchMap an ArrayParamsDispatchEntry that will handle the
// caller's LLSD::Array.
addArrayParamsDispatchEntry(name, desc, make_invoker(f),
@@ -493,33 +369,76 @@ void LLEventDispatcher::add(const std::string& name, const std::string& desc, Me
addMapParamsDispatchEntry(name, desc, make_invoker(f, getter), params, defaults);
}
+// general case, when f() has a non-void return type
+template <typename RETURNTYPE>
+struct LLEventDispatcher::ReturnLLSD
+{
+ template <typename Function>
+ LLSD operator()(Function f, const LLSD& args)
+ {
+ return { LL::apply(f, args) };
+ }
+
+ template <typename Method, typename InstanceGetter>
+ LLSD operator()(Method f, const InstanceGetter& getter, const LLSD& args)
+ {
+ constexpr auto arity = boost::function_types::function_arity<
+ typename std::remove_reference<Method>::type>::value - 1;
+
+ // Use bind_front() to bind the method to (a pointer to) the object
+ // returned by getter(). It's okay to capture and bind a pointer
+ // because this bind_front() object will last only as long as this
+ // operator() call.
+ return { LL::apply_n<arity>(LL::bind_front(f, LL::get_ptr(getter())), args) };
+ }
+};
+
+// specialize for void return type
+template <>
+struct LLEventDispatcher::ReturnLLSD<void>
+{
+ template <typename Function>
+ LLSD operator()(Function f, const LLSD& args)
+ {
+ LL::apply(f, args);
+ return {};
+ }
+
+ template <typename Method, typename InstanceGetter>
+ LLSD operator()(Method f, const InstanceGetter& getter, const LLSD& args)
+ {
+ constexpr auto arity = boost::function_types::function_arity<
+ typename std::remove_reference<Method>::type>::value - 1;
+
+ // Use bind_front() to bind the method to (a pointer to) the object
+ // returned by getter(). It's okay to capture and bind a pointer
+ // because this bind_front() object will last only as long as this
+ // operator() call.
+ LL::apply_n<arity>(LL::bind_front(f, LL::get_ptr(getter())), args);
+ return {};
+ }
+};
+
template <typename Function>
LLEventDispatcher::invoker_function
LLEventDispatcher::make_invoker(Function f)
{
- // Step 1 of parameter analysis, the top of the recursion. Passing a
- // suitable f (see add()'s enable_if condition) to this method causes it
- // to infer the function type; specifying that function type to invoker<>
- // causes it to fill in the begin/end MPL iterators over the function's
- // list of parameter types.
- // While normally invoker::apply() could infer its template type from the
- // boost::fusion::nil parameter value, here we must be explicit since
- // we're boost::bind()ing it rather than calling it directly.
- return boost::bind(&invoker<Function>::template apply<boost::fusion::nil>,
- f,
- _1,
- boost::fusion::nil());
+ return [f](const LLSD& args)
+ {
+ return ReturnLLSD<typename boost::function_types::result_type<Function>::type>()
+ (f, args);
+ };
}
template <typename Method, typename InstanceGetter>
LLEventDispatcher::invoker_function
LLEventDispatcher::make_invoker(Method f, const InstanceGetter& getter)
{
- // Use invoker::method_apply() to treat the instance (first) arg specially.
- return boost::bind(&invoker<Method>::template method_apply<InstanceGetter>,
- f,
- _1,
- getter);
+ return [f, getter](const LLSD& args)
+ {
+ return ReturnLLSD<typename boost::function_types::result_type<Method>::type>()
+ (f, getter, args);
+ };
}
/*****************************************************************************
diff --git a/indra/llcommon/llptrto.h b/indra/llcommon/llptrto.h
index 4082e30de6..9ef279fdbf 100644
--- a/indra/llcommon/llptrto.h
+++ b/indra/llcommon/llptrto.h
@@ -33,9 +33,12 @@
#include "llpointer.h"
#include "llrefcount.h" // LLRefCount
+#include <boost/intrusive_ptr.hpp>
+#include <boost/shared_ptr.hpp>
#include <boost/type_traits/is_base_of.hpp>
#include <boost/type_traits/remove_pointer.hpp>
-#include <boost/utility/enable_if.hpp>
+#include <memory> // std::shared_ptr, std::unique_ptr
+#include <type_traits>
/**
* LLPtrTo<TARGET>::type is either of two things:
@@ -55,14 +58,14 @@ struct LLPtrTo
/// specialize for subclasses of LLRefCount
template <class T>
-struct LLPtrTo<T, typename boost::enable_if< boost::is_base_of<LLRefCount, T> >::type>
+struct LLPtrTo<T, typename std::enable_if< boost::is_base_of<LLRefCount, T>::value >::type>
{
typedef LLPointer<T> type;
};
/// specialize for subclasses of LLThreadSafeRefCount
template <class T>
-struct LLPtrTo<T, typename boost::enable_if< boost::is_base_of<LLThreadSafeRefCount, T> >::type>
+struct LLPtrTo<T, typename std::enable_if< boost::is_base_of<LLThreadSafeRefCount, T>::value >::type>
{
typedef LLPointer<T> type;
};
@@ -83,4 +86,83 @@ struct LLRemovePointer< LLPointer<SOMECLASS> >
typedef SOMECLASS type;
};
+namespace LL
+{
+
+/*****************************************************************************
+* get_ref()
+*****************************************************************************/
+ template <typename T>
+ struct GetRef
+ {
+ // return const ref or non-const ref, depending on whether we can bind
+ // a non-const lvalue ref to the argument
+ const auto& operator()(const T& obj) const { return obj; }
+ auto& operator()(T& obj) const { return obj; }
+ };
+
+ template <typename T>
+ struct GetRef<const T*>
+ {
+ const auto& operator()(const T* ptr) const { return *ptr; }
+ };
+
+ template <typename T>
+ struct GetRef<T*>
+ {
+ auto& operator()(T* ptr) const { return *ptr; }
+ };
+
+ template <typename T>
+ struct GetRef< LLPointer<T> >
+ {
+ auto& operator()(LLPointer<T> ptr) const { return *ptr; }
+ };
+
+ /// whether we're passed a pointer or a reference, return a reference
+ template <typename T>
+ auto& get_ref(T& ptr_or_ref)
+ {
+ return GetRef<typename std::decay<T>::type>()(ptr_or_ref);
+ }
+
+ template <typename T>
+ const auto& get_ref(const T& ptr_or_ref)
+ {
+ return GetRef<typename std::decay<T>::type>()(ptr_or_ref);
+ }
+
+/*****************************************************************************
+* get_ptr()
+*****************************************************************************/
+ // if T is any pointer type we recognize, return it unchanged
+ template <typename T>
+ const T* get_ptr(const T* ptr) { return ptr; }
+
+ template <typename T>
+ T* get_ptr(T* ptr) { return ptr; }
+
+ template <typename T>
+ const std::shared_ptr<T>& get_ptr(const std::shared_ptr<T>& ptr) { return ptr; }
+
+ template <typename T>
+ const std::unique_ptr<T>& get_ptr(const std::unique_ptr<T>& ptr) { return ptr; }
+
+ template <typename T>
+ const boost::shared_ptr<T>& get_ptr(const boost::shared_ptr<T>& ptr) { return ptr; }
+
+ template <typename T>
+ const boost::intrusive_ptr<T>& get_ptr(const boost::intrusive_ptr<T>& ptr) { return ptr; }
+
+ template <typename T>
+ const LLPointer<T>& get_ptr(const LLPointer<T>& ptr) { return ptr; }
+
+ // T is not any pointer type we recognize, take a pointer to the parameter
+ template <typename T>
+ const T* get_ptr(const T& obj) { return &obj; }
+
+ template <typename T>
+ T* get_ptr(T& obj) { return &obj; }
+} // namespace LL
+
#endif /* ! defined(LL_LLPTRTO_H) */
diff --git a/indra/llcommon/llsdutil.cpp b/indra/llcommon/llsdutil.cpp
index f70bee9903..e98fc0285a 100644
--- a/indra/llcommon/llsdutil.cpp
+++ b/indra/llcommon/llsdutil.cpp
@@ -1046,3 +1046,38 @@ LLSD llsd_shallow(LLSD value, LLSD filter)
return shallow;
}
+
+LLSD LL::apply_llsd_fix(size_t arity, const LLSD& args)
+{
+ // LLSD supports a number of types, two of which are aggregates: Map and
+ // Array. We don't try to support Map: supporting Map would seem to
+ // promise that we could somehow match the string key to 'func's parameter
+ // names. Uh sorry, maybe in some future version of C++ with reflection.
+ if (args.isMap())
+ {
+ LLTHROW(LL::apply_error("LL::apply(function, Map LLSD) unsupported"));
+ }
+ // We expect an LLSD array, but what the heck, treat isUndefined() as a
+ // zero-length array for calling a nullary 'func'.
+ if (args.isUndefined() || args.isArray())
+ {
+ // this works because LLSD().size() == 0
+ if (args.size() != arity)
+ {
+ LLTHROW(LL::apply_error(stringize("LL::apply(function(", arity, " args), ",
+ args.size(), "-entry LLSD array)")));
+ }
+ return args;
+ }
+
+ // args is one of the scalar types
+ // scalar_LLSD.size() == 0, so don't test that here.
+ // You can pass a scalar LLSD only to a unary 'func'.
+ if (arity != 1)
+ {
+ LLTHROW(LL::apply_error(stringize("LL::apply(function(", arity, " args), "
+ "LLSD ", LLSD::typeString(args.type()), ")")));
+ }
+ // make an array of it
+ return llsd::array(args);
+}
diff --git a/indra/llcommon/llsdutil.h b/indra/llcommon/llsdutil.h
index eddaa64bd2..546e27930d 100644
--- a/indra/llcommon/llsdutil.h
+++ b/indra/llcommon/llsdutil.h
@@ -29,10 +29,12 @@
#ifndef LL_LLSDUTIL_H
#define LL_LLSDUTIL_H
+#include "apply.h" // LL::invoke()
#include "llsd.h"
#include <boost/functional/hash.hpp>
-#include <boost/type_traits.hpp>
+#include <boost/function_types/function_arity.hpp>
#include <cassert>
+#include <type_traits>
// U32
LL_COMMON_API LLSD ll_sd_from_U32(const U32);
@@ -589,6 +591,10 @@ namespace LL
/*****************************************************************************
* apply(function, LLSD array)
*****************************************************************************/
+// validate incoming LLSD blob, and return an LLSD array suitable to pass to
+// apply_impl()
+LLSD apply_llsd_fix(size_t arity, const LLSD& args);
+
// Derived from https://stackoverflow.com/a/20441189
// and https://en.cppreference.com/w/cpp/utility/apply .
// We can't simply make a tuple from the LLSD array and then apply() that
@@ -602,6 +608,16 @@ auto apply_impl(CALLABLE&& func, const LLSD& array, std::index_sequence<I...>)
return std::forward<CALLABLE>(func)(LLSDParam<LLSD>(array[I])...);
}
+// use apply_n<ARITY>(function, LLSD) to call a specific arity of a variadic
+// function with (that many) items from the passed LLSD array
+template <size_t ARITY, typename CALLABLE>
+auto apply_n(CALLABLE&& func, const LLSD& args)
+{
+ return apply_impl(std::forward<CALLABLE>(func),
+ apply_llsd_fix(ARITY, args),
+ std::make_index_sequence<ARITY>());
+}
+
/**
* apply(function, LLSD) goes beyond C++17 std::apply(). For this case
* @a function @emph cannot be variadic: the compiler must know at compile
@@ -610,32 +626,11 @@ auto apply_impl(CALLABLE&& func, const LLSD& array, std::index_sequence<I...>)
template <typename CALLABLE>
auto apply(CALLABLE&& func, const LLSD& args)
{
- LLSD array;
- constexpr auto arity = boost::function_traits<CALLABLE>::arity;
- // LLSD supports a number of types, two of which are aggregates: Map and
- // Array. We don't try to support Map: supporting Map would seem to
- // promise that we could somehow match the string key to 'func's parameter
- // names. Uh sorry, maybe in some future version of C++ with reflection.
- assert(! args.isMap());
- // We expect an LLSD array, but what the heck, treat isUndefined() as a
- // zero-length array for calling a nullary 'func'.
- if (args.isUndefined() || args.isArray())
- {
- // this works because LLSD().size() == 0
- assert(args.size() == arity);
- array = args;
- }
- else // args is one of the scalar types
- {
- // scalar_LLSD.size() == 0, so don't test that here.
- // You can pass a scalar LLSD only to a unary 'func'.
- assert(arity == 1);
- // make an array of it
- array = llsd::array(args);
- }
- return apply_impl(std::forward<CALLABLE>(func),
- array,
- std::make_index_sequence<arity>());
+ // infer arity from the definition of func
+ constexpr auto arity = boost::function_types::function_arity<
+ typename std::remove_reference<CALLABLE>::type>::value;
+ // now that we have a compile-time arity, apply_n() works
+ return apply_n<arity>(std::forward<CALLABLE>(func), args);
}
} // namespace LL
diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp
index b38e47a773..f09dd63316 100644
--- a/indra/llcommon/tests/lleventdispatcher_test.cpp
+++ b/indra/llcommon/tests/lleventdispatcher_test.cpp
@@ -33,8 +33,6 @@
#include <boost/bind.hpp>
#include <boost/function.hpp>
#include <boost/range.hpp>
-#include <boost/foreach.hpp>
-#define foreach BOOST_FOREACH
#include <boost/lambda/lambda.hpp>
@@ -206,7 +204,7 @@ struct Vars
void methodnb(NPARAMSb)
{
std::ostringstream vbin;
- foreach(U8 byte, bin)
+ for (U8 byte: bin)
{
vbin << std::hex << std::setfill('0') << std::setw(2) << unsigned(byte);
}
@@ -462,7 +460,7 @@ namespace tut
debug("dft_array_full:\n",
dft_array_full);
// Partial defaults arrays.
- foreach(LLSD::String a, ab)
+ for (LLSD::String a: ab)
{
LLSD::Integer partition(std::min(partial_offset, dft_array_full[a].size()));
dft_array_partial[a] =
@@ -472,7 +470,7 @@ namespace tut
debug("dft_array_partial:\n",
dft_array_partial);
- foreach(LLSD::String a, ab)
+ for(LLSD::String a: ab)
{
// Generate full defaults maps by zipping (params, dft_array_full).
dft_map_full[a] = zipmap(params[a], dft_array_full[a]);
@@ -599,19 +597,14 @@ namespace tut
{
// Copy descs to a temp map of same type.
DescMap forgotten(descs.begin(), descs.end());
- // LLEventDispatcher intentionally provides only const_iterator:
- // since dereferencing that iterator generates values on the fly,
- // it's meaningless to have a modifiable iterator. But since our
- // 'work' object isn't const, by default BOOST_FOREACH() wants to
- // use non-const iterators. Persuade it to use the const_iterator.
- foreach(LLEventDispatcher::NameDesc nd, const_cast<const Dispatcher&>(work))
+ for (LLEventDispatcher::NameDesc nd: work)
{
DescMap::iterator found = forgotten.find(nd.first);
- ensure(STRINGIZE("LLEventDispatcher records function '" << nd.first
- << "' we didn't enter"),
+ ensure(stringize("LLEventDispatcher records function '", nd.first,
+ "' we didn't enter"),
found != forgotten.end());
- ensure_equals(STRINGIZE("LLEventDispatcher desc '" << nd.second <<
- "' doesn't match what we entered: '" << found->second << "'"),
+ ensure_equals(stringize("LLEventDispatcher desc '", nd.second,
+ "' doesn't match what we entered: '", found->second, "'"),
nd.second, found->second);
// found in our map the name from LLEventDispatcher, good, erase
// our map entry
@@ -622,26 +615,26 @@ namespace tut
std::ostringstream out;
out << "LLEventDispatcher failed to report";
const char* delim = ": ";
- foreach(const DescMap::value_type& fme, forgotten)
+ for (const DescMap::value_type& fme: forgotten)
{
out << delim << fme.first;
delim = ", ";
}
- ensure(out.str(), false);
+ throw failure(out.str());
}
}
Vars* varsfor(const std::string& name)
{
VarsMap::const_iterator found = funcvars.find(name);
- ensure(STRINGIZE("No Vars* for " << name), found != funcvars.end());
- ensure(STRINGIZE("NULL Vars* for " << name), found->second);
+ ensure(stringize("No Vars* for ", name), found != funcvars.end());
+ ensure(stringize("NULL Vars* for ", name), found->second);
return found->second;
}
void ensure_has(const std::string& outer, const std::string& inner)
{
- ensure(STRINGIZE("'" << outer << "' does not contain '" << inner << "'").c_str(),
+ ensure(stringize("'", outer, "' does not contain '", inner, "'").c_str(),
outer.find(inner) != std::string::npos);
}
@@ -689,7 +682,7 @@ namespace tut
LLSD getMetadata(const std::string& name)
{
LLSD meta(work.getMetadata(name));
- ensure(STRINGIZE("No metadata for " << name), meta.isDefined());
+ ensure(stringize("No metadata for ", name), meta.isDefined());
return meta;
}
@@ -869,12 +862,12 @@ namespace tut
LLSD req(LLSD::emptyArray());
if (arity)
req[arity - 1] = LLSD();
- foreach(LLSD nm, inArray(names))
+ for (LLSD nm: inArray(names))
{
LLSD metadata(getMetadata(nm));
ensure_equals("name mismatch", metadata["name"], nm);
ensure_equals(metadata["desc"].asString(), descs[nm]);
- ensure_equals(STRINGIZE("mismatched required for " << nm.asString()),
+ ensure_equals(stringize("mismatched required for ", nm.asString()),
metadata["required"], req);
ensure("should not have optional", metadata["optional"].isUndefined());
}
@@ -932,8 +925,8 @@ namespace tut
ensure_equals("mdft name", mdft, mmeta["name"]);
ameta.erase("name");
mmeta.erase("name");
- ensure_equals(STRINGIZE("metadata for " << adft.asString()
- << " vs. " << mdft.asString()),
+ ensure_equals(stringize("metadata for ", adft.asString(),
+ " vs. ", mdft.asString()),
ameta, mmeta);
}
}
@@ -949,7 +942,7 @@ namespace tut
// params are required. Also maps containing left requirements for
// partial defaults arrays. Also defaults maps from defaults arrays.
LLSD allreq, leftreq, rightdft;
- foreach(LLSD::String a, ab)
+ for (LLSD::String a: ab)
{
// The map in which all params are required uses params[a] as
// keys, with all isUndefined() as values. We can accomplish that
@@ -977,9 +970,9 @@ namespace tut
// Generate maps containing parameter names not provided by the
// dft_map_partial maps.
LLSD skipreq(allreq);
- foreach(LLSD::String a, ab)
+ for (LLSD::String a: ab)
{
- foreach(const MapEntry& me, inMap(dft_map_partial[a]))
+ for (const MapEntry& me: inMap(dft_map_partial[a]))
{
skipreq[a].erase(me.first);
}
@@ -1024,7 +1017,7 @@ namespace tut
(llsd::array("freenb_map_mdft", "smethodnb_map_mdft", "methodnb_map_mdft"),
llsd::array(LLSD::emptyMap(), dft_map_full["b"])))); // required, optional
- foreach(LLSD grp, inArray(groups))
+ for (LLSD grp: inArray(groups))
{
// Internal structure of each group in 'groups':
LLSD names(grp[0]);
@@ -1037,14 +1030,14 @@ namespace tut
optional);
// Loop through 'names'
- foreach(LLSD nm, inArray(names))
+ for (LLSD nm: inArray(names))
{
LLSD metadata(getMetadata(nm));
ensure_equals("name mismatch", metadata["name"], nm);
ensure_equals(nm.asString(), metadata["desc"].asString(), descs[nm]);
- ensure_equals(STRINGIZE(nm << " required mismatch"),
+ ensure_equals(stringize(nm, " required mismatch"),
metadata["required"], required);
- ensure_equals(STRINGIZE(nm << " optional mismatch"),
+ ensure_equals(stringize(nm, " optional mismatch"),
metadata["optional"], optional);
}
}
@@ -1107,7 +1100,7 @@ namespace tut
// LLSD value matching 'required' according to llsd_matches() rules.
LLSD matching(LLSDMap("d", 3.14)("array", llsd::array("answer", true, answer)));
// Okay, walk through 'tests'.
- foreach(const CallablesTriple& tr, tests)
+ for (const CallablesTriple& tr: tests)
{
// Should be able to pass 'answer' to Callables registered
// without 'required'.
@@ -1129,14 +1122,17 @@ namespace tut
set_test_name("passing wrong args to (map | array)-style registrations");
// Pass scalar/map to array-style functions, scalar/array to map-style
- // functions. As that validation happens well before we engage the
- // argument magic, it seems pointless to repeat this with every
- // variation: (free function | non-static method), (no | arbitrary)
- // args. We should only need to engage it for one map-style
- // registration and one array-style registration.
- std::string array_exc("needs an args array");
- call_logerr("free0_array", 17, array_exc);
- call_logerr("free0_array", LLSDMap("pi", 3.14), array_exc);
+ // functions. It seems pointless to repeat this with every variation:
+ // (free function | non-static method), (no | arbitrary) args. We
+ // should only need to engage it for one map-style registration and
+ // one array-style registration.
+ // Now that LLEventDispatcher has been extended to treat an LLSD
+ // scalar as a single-entry array, the error we expect in this case is
+ // that apply() is trying to pass that non-empty array to a nullary
+ // function.
+ call_logerr("free0_array", 17, "LL::apply");
+ // similarly, apply() doesn't accept an LLSD Map
+ call_logerr("free0_array", LLSDMap("pi", 3.14), "unsupported");
std::string map_exc("needs a map");
call_logerr("free0_map", 17, map_exc);
@@ -1178,15 +1174,21 @@ namespace tut
template<> template<>
void object::test<19>()
{
- set_test_name("call array-style functions with too-short arrays");
- // Could have two different too-short arrays, one for *na and one for
- // *nb, but since they both take 5 params...
+ set_test_name("call array-style functions with wrong-length arrays");
+ // Could have different wrong-length arrays for *na and for *nb, but
+ // since they both take 5 params...
LLSD tooshort(llsd::array("this", "array", "too", "short"));
- foreach(const LLSD& funcsab, inArray(array_funcs))
+ LLSD toolong (llsd::array("this", "array", "is", "one", "too", "long"));
+ LLSD badargs (llsd::array(tooshort, toolong));
+ for (const LLSD& toosomething: inArray(badargs))
{
- foreach(const llsd::MapEntry& e, inMap(funcsab))
+ for (const LLSD& funcsab: inArray(array_funcs))
{
- call_logerr(e.second, tooshort, "requires more arguments");
+ for (const llsd::MapEntry& e: inMap(funcsab))
+ {
+ // apply() complains about wrong number of array entries
+ call_logerr(e.second, toosomething, "LL::apply");
+ }
}
}
}
@@ -1206,40 +1208,25 @@ namespace tut
LLDate("2011-02-03T15:07:00Z"),
LLURI("http://secondlife.com"),
binary)));
- LLSD argsplus(args);
- argsplus["a"].append("bogus");
- argsplus["b"].append("bogus");
LLSD expect;
- foreach(LLSD::String a, ab)
+ for (LLSD::String a: ab)
{
expect[a] = zipmap(params[a], args[a]);
}
// Adjust expect["a"]["cp"] for special Vars::cp treatment.
- expect["a"]["cp"] = std::string("'") + expect["a"]["cp"].asString() + "'";
+ expect["a"]["cp"] = stringize("'", expect["a"]["cp"].asString(), "'");
debug("expect: ", expect);
- // Use substantially the same logic for args and argsplus
- LLSD argsarrays(llsd::array(args, argsplus));
- // So i==0 selects 'args', i==1 selects argsplus
- for (LLSD::Integer i(0), iend(argsarrays.size()); i < iend; ++i)
+ for (const LLSD& funcsab: inArray(array_funcs))
{
- foreach(const LLSD& funcsab, inArray(array_funcs))
+ for (LLSD::String a: ab)
{
- foreach(LLSD::String a, ab)
- {
- // Reset the Vars instance before each call
- Vars* vars(varsfor(funcsab[a]));
- *vars = Vars();
- work(funcsab[a], argsarrays[i][a]);
- ensure_llsd(STRINGIZE(funcsab[a].asString() <<
- ": expect[\"" << a << "\"] mismatch"),
- vars->inspect(), expect[a], 7); // 7 bits ~= 2 decimal digits
-
- // TODO: in the i==1 or argsplus case, intercept LL_WARNS
- // output? Even without that, using argsplus verifies that
- // passing too many args isn't fatal; it works -- but
- // would be nice to notice the warning too.
- }
+ // Reset the Vars instance before each call
+ Vars* vars(varsfor(funcsab[a]));
+ *vars = Vars();
+ work(funcsab[a], args[a]);
+ ensure_llsd(stringize(funcsab[a].asString(), ": expect[\"", a, "\"] mismatch"),
+ vars->inspect(), expect[a], 7); // 7 bits ~= 2 decimal digits
}
}
}
@@ -1267,7 +1254,7 @@ namespace tut
("a", llsd::array(false, 255, 98.6, 1024.5, "pointer"))
("b", llsd::array("object", LLUUID::generateNewID(), LLDate::now(), LLURI("http://wiki.lindenlab.com/wiki"), LLSD::Binary(boost::begin(binary), boost::end(binary)))));
LLSD array_overfull(array_full);
- foreach(LLSD::String a, ab)
+ for (LLSD::String a: ab)
{
array_overfull[a].append("bogus");
}
@@ -1281,7 +1268,7 @@ namespace tut
ensure_not_equals("UUID collision",
array_full["b"][1].asUUID(), dft_array_full["b"][1].asUUID());
LLSD map_full, map_overfull;
- foreach(LLSD::String a, ab)
+ for (LLSD::String a: ab)
{
map_full[a] = zipmap(params[a], array_full[a]);
map_overfull[a] = map_full[a];
@@ -1324,15 +1311,15 @@ namespace tut
LLSD argssets(llsd::array(array_full, array_overfull, map_full, map_overfull));
foreach(const LLSD& args, inArray(argssets))
{
- foreach(LLSD::String a, ab)
+ for (LLSD::String a: ab)
{
- foreach(LLSD::String name, inArray(names[a]))
+ for (LLSD::String name: inArray(names[a]))
{
// Reset the Vars instance
Vars* vars(varsfor(name));
*vars = Vars();
work(name, args[a]);
- ensure_llsd(STRINGIZE(name << ": expect[\"" << a << "\"] mismatch"),
+ ensure_llsd(stringize(name, ": expect[\"", a, "\"] mismatch"),
vars->inspect(), expect[a], 7); // 7 bits, 2 decimal digits
// intercept LL_WARNS for the two overfull cases?
}