From 2770d6f8813b6bfef89e80dfaf091287c8c0eb4d Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 28 Jan 2011 20:14:38 -0500 Subject: Introduce LLSDArray, LLSDMap, LLSDParam. LLSDArray is a helper to construct an LLSD::Array value inline. LLSDMap is a helper to construct an LLSD::Map value inline. LLSDParam is a customization point, a way for generic code to support unforseen parameter types as conversion targets for LLSD values. --- indra/llcommon/llsdutil.h | 188 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 188 insertions(+) (limited to 'indra/llcommon/llsdutil.h') diff --git a/indra/llcommon/llsdutil.h b/indra/llcommon/llsdutil.h index bb8c0690b1..58ccc59f5e 100644 --- a/indra/llcommon/llsdutil.h +++ b/indra/llcommon/llsdutil.h @@ -138,4 +138,192 @@ template LLSD llsd_copy_array(Input iter, Input end) return dest; } +/***************************************************************************** +* LLSDArray +*****************************************************************************/ +/** + * Construct an LLSD::Array inline, with implicit conversion to LLSD. Usage: + * + * @code + * void somefunc(const LLSD&); + * ... + * somefunc(LLSDArray("text")(17)(3.14)); + * @endcode + * + * For completeness, LLSDArray() with no args constructs an empty array, so + * LLSDArray()("text")(17)(3.14) produces an array equivalent to the + * above. But for most purposes, LLSD() is already equivalent to an empty + * array, and if you explicitly want an empty isArray(), there's + * LLSD::emptyArray(). However, supporting a no-args LLSDArray() constructor + * follows the principle of least astonishment. + */ +class LLSDArray +{ +public: + LLSDArray(): + _data(LLSD::emptyArray()) + {} + LLSDArray(const LLSD& value): + _data(LLSD::emptyArray()) + { + _data.append(value); + } + + LLSDArray& operator()(const LLSD& value) + { + _data.append(value); + return *this; + } + + operator LLSD() const { return _data; } + LLSD get() const { return _data; } + +private: + LLSD _data; +}; + +/***************************************************************************** +* LLSDMap +*****************************************************************************/ +/** + * Construct an LLSD::Map inline, with implicit conversion to LLSD. Usage: + * + * @code + * void somefunc(const LLSD&); + * ... + * somefunc(LLSDMap("alpha", "abc")("number", 17)("pi", 3.14)); + * @endcode + * + * For completeness, LLSDMap() with no args constructs an empty map, so + * LLSDMap()("alpha", "abc")("number", 17)("pi", 3.14) produces a map + * equivalent to the above. But for most purposes, LLSD() is already + * equivalent to an empty map, and if you explicitly want an empty isMap(), + * there's LLSD::emptyMap(). However, supporting a no-args LLSDMap() + * constructor follows the principle of least astonishment. + */ +class LLSDMap +{ +public: + LLSDMap(): + _data(LLSD::emptyMap()) + {} + LLSDMap(const LLSD::String& key, const LLSD& value): + _data(LLSD::emptyMap()) + { + _data[key] = value; + } + + LLSDMap& operator()(const LLSD::String& key, const LLSD& value) + { + _data[key] = value; + return *this; + } + + operator LLSD() const { return _data; } + LLSD get() const { return _data; } + +private: + LLSD _data; +}; + +/***************************************************************************** +* LLSDParam +*****************************************************************************/ +/** + * LLSDParam is a customization point for passing LLSD values to function + * parameters of more or less arbitrary type. LLSD provides a small set of + * native conversions; but if a generic algorithm explicitly constructs an + * LLSDParam object in the function's argument list, a consumer can provide + * LLSDParam specializations to support more different parameter types than + * LLSD's native conversions. + * + * Usage: + * + * @code + * void somefunc(const paramtype&); + * ... + * somefunc(..., LLSDParam(someLLSD), ...); + * @endcode + */ +template +class LLSDParam +{ +public: + /** + * Default implementation converts to T on construction, saves converted + * value for later retrieval + */ + LLSDParam(const LLSD& value): + _value(value) + {} + + operator T() const { return _value; } + +private: + T _value; +}; + +/** + * LLSDParam is an example of the kind of conversion you can + * support with LLSDParam beyond native LLSD conversions. Normally you can't + * pass an LLSD object to a function accepting const char* -- but you can + * safely pass an LLSDParam(yourLLSD). + */ +template <> +class LLSDParam +{ +private: + // The difference here is that we store a std::string rather than a const + // char*. It's important that the LLSDParam object own the std::string. + std::string _value; + // We don't bother storing the incoming LLSD object, but we do have to + // distinguish whether _value is an empty string because the LLSD object + // contains an empty string or because it's isUndefined(). + bool _undefined; + +public: + LLSDParam(const LLSD& value): + _value(value), + _undefined(value.isUndefined()) + {} + + // The const char* we retrieve is for storage owned by our _value member. + // That's how we guarantee that the const char* is valid for the lifetime + // of this LLSDParam object. Constructing your LLSDParam in the argument + // list should ensure that the LLSDParam object will persist for the + // duration of the function call. + operator const char*() const + { + if (_undefined) + { + // By default, an isUndefined() LLSD object's asString() method + // will produce an empty string. But for a function accepting + // const char*, it's often important to be able to pass NULL, and + // isUndefined() seems like the best way. If you want to pass an + // empty string, you can still pass LLSD(""). Without this special + // case, though, no LLSD value could pass NULL. + return NULL; + } + return _value.c_str(); + } +}; + +/** + * LLSDParam resolves conversion ambiguity. g++ considers F64, S32 and + * bool equivalent candidates for implicit conversion to float. (/me rolls eyes) + */ +template <> +class LLSDParam +{ +private: + float _value; + +public: + LLSDParam(const LLSD& value): + _value(value.asReal()) + {} + + operator float() const { return _value; } +}; + #endif // LL_LLSDUTIL_H -- cgit v1.3 From 8b7c903e5cf5620deaab09ec39e978d62ddb45c3 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 31 Jan 2011 18:00:58 -0500 Subject: Fix a couple gotchas in LLSDArray, LLSDParam, llsd_equals(). Nested LLSDArray expressions, e.g.: LLSD array_of_arrays(LLSDArray(LLSDArray(17)(34)) (LLSDArray("x")("y"))); would quietly produce bad results because the outermost LLSDArray was being constructed with the compiler's implicit LLSDArray(const LLSDArray&) rather than LLSDArray(const LLSD&) as the reader assumes. Fixed with an explicit copy constructor to Do The Right Thing. Generalized LLSDParam specialization into a macro to resolve similar conversion ambiguities for float, LLUUID, LLDate, LLURI and LLSD::Binary. Added optional bits= argument to llsd_equals() to permit comparing embedded Real values using is_approx_equal_fraction() rather than strictly bitwise. Omitting bits= retains current bitwise-comparison behavior. --- indra/llcommon/llsdutil.cpp | 27 ++++++++++----- indra/llcommon/llsdutil.h | 82 ++++++++++++++++++++++++++++++++++----------- 2 files changed, 81 insertions(+), 28 deletions(-) (limited to 'indra/llcommon/llsdutil.h') diff --git a/indra/llcommon/llsdutil.cpp b/indra/llcommon/llsdutil.cpp index f8f9ece058..803417d368 100644 --- a/indra/llcommon/llsdutil.cpp +++ b/indra/llcommon/llsdutil.cpp @@ -41,6 +41,7 @@ #include "llsdserialize.h" #include "stringize.h" +#include "is_approx_equal_fraction.h" #include #include @@ -571,7 +572,7 @@ std::string llsd_matches(const LLSD& prototype, const LLSD& data, const std::str return match_types(prototype.type(), TypeVector(), data.type(), pfx); } -bool llsd_equals(const LLSD& lhs, const LLSD& rhs) +bool llsd_equals(const LLSD& lhs, const LLSD& rhs, unsigned bits) { // We're comparing strict equality of LLSD representation rather than // performing any conversions. So if the types aren't equal, the LLSD @@ -588,6 +589,20 @@ bool llsd_equals(const LLSD& lhs, const LLSD& rhs) // Both are TypeUndefined. There's nothing more to know. return true; + case LLSD::TypeReal: + // This is where the 'bits' argument comes in handy. If passed + // explicitly, it means to use is_approx_equal_fraction() to compare. + if (bits >= 0) + { + return is_approx_equal_fraction(lhs.asReal(), rhs.asReal(), bits); + } + // Otherwise we compare bit representations, and the usual caveats + // about comparing floating-point numbers apply. Omitting 'bits' when + // comparing Real values is only useful when we expect identical bit + // representation for a given Real value, e.g. for integer-valued + // Reals. + return (lhs.asReal() == rhs.asReal()); + #define COMPARE_SCALAR(type) \ case LLSD::Type##type: \ /* LLSD::URI has operator!=() but not operator==() */ \ @@ -596,10 +611,6 @@ bool llsd_equals(const LLSD& lhs, const LLSD& rhs) COMPARE_SCALAR(Boolean); COMPARE_SCALAR(Integer); - // The usual caveats about comparing floating-point numbers apply. This is - // only useful when we expect identical bit representation for a given - // Real value, e.g. for integer-valued Reals. - COMPARE_SCALAR(Real); COMPARE_SCALAR(String); COMPARE_SCALAR(UUID); COMPARE_SCALAR(Date); @@ -617,7 +628,7 @@ bool llsd_equals(const LLSD& lhs, const LLSD& rhs) for ( ; lai != laend && rai != raend; ++lai, ++rai) { // If any one array element is unequal, the arrays are unequal. - if (! llsd_equals(*lai, *rai)) + if (! llsd_equals(*lai, *rai, bits)) return false; } // Here we've reached the end of one or the other array. They're equal @@ -644,7 +655,7 @@ bool llsd_equals(const LLSD& lhs, const LLSD& rhs) if (rhskeys.erase(lmi->first) != 1) return false; // Both maps have the current key. Compare values. - if (! llsd_equals(lmi->second, rhs[lmi->first])) + if (! llsd_equals(lmi->second, rhs[lmi->first], bits)) return false; } // We've now established that all the lhs keys have equal values in @@ -657,7 +668,7 @@ bool llsd_equals(const LLSD& lhs, const LLSD& rhs) // We expect that every possible type() value is specifically handled // above. Failing to extend this switch to support a new LLSD type is // an error that must be brought to the coder's attention. - LL_ERRS("llsd_equals") << "llsd_equals(" << lhs << ", " << rhs << "): " + LL_ERRS("llsd_equals") << "llsd_equals(" << lhs << ", " << rhs << ", " << bits << "): " "unknown type " << lhs.type() << LL_ENDL; return false; // pacify the compiler } diff --git a/indra/llcommon/llsdutil.h b/indra/llcommon/llsdutil.h index 58ccc59f5e..c873b17112 100644 --- a/indra/llcommon/llsdutil.h +++ b/indra/llcommon/llsdutil.h @@ -123,8 +123,10 @@ LL_COMMON_API BOOL compare_llsd_with_template( */ LL_COMMON_API std::string llsd_matches(const LLSD& prototype, const LLSD& data, const std::string& pfx=""); -/// Deep equality -LL_COMMON_API bool llsd_equals(const LLSD& lhs, const LLSD& rhs); +/// Deep equality. If you want to compare LLSD::Real values for approximate +/// equality rather than bitwise equality, pass @a bits as for +/// is_approx_equal_fraction(). +LL_COMMON_API bool llsd_equals(const LLSD& lhs, const LLSD& rhs, unsigned bits=-1); // Simple function to copy data out of input & output iterators if // there is no need for casting. @@ -163,6 +165,31 @@ public: LLSDArray(): _data(LLSD::emptyArray()) {} + + /** + * Need an explicit copy constructor. Consider the following: + * + * @code + * LLSD array_of_arrays(LLSDArray(LLSDArray(17)(34)) + * (LLSDArray("x")("y"))); + * @endcode + * + * The coder intends to construct [[17, 34], ["x", "y"]]. + * + * With the compiler's implicit copy constructor, s/he gets instead + * [17, 34, ["x", "y"]]. + * + * The expression LLSDArray(17)(34) constructs an LLSDArray with those two + * values. The reader assumes it should be converted to LLSD, as we always + * want with LLSDArray, before passing it to the @em outer LLSDArray + * constructor! This copy constructor makes that happen. + */ + LLSDArray(const LLSDArray& inner): + _data(LLSD::emptyArray()) + { + _data.append(inner); + } + LLSDArray(const LLSD& value): _data(LLSD::emptyArray()) { @@ -263,6 +290,39 @@ private: T _value; }; +/** + * Turns out that several target types could accept an LLSD param using any of + * a few different conversions, e.g. LLUUID's constructor can accept LLUUID or + * std::string. Therefore, the compiler can't decide which LLSD conversion + * operator to choose, even though to us it seems obvious. But that's okay, we + * can specialize LLSDParam for such target types, explicitly specifying the + * desired conversion -- that's part of what LLSDParam is all about. Turns out + * we have to do that enough to make it worthwhile generalizing. Use a macro + * because I need to specify one of the asReal, etc., explicit conversion + * methods as well as a type. If I'm overlooking a clever way to implement + * that using a template instead, feel free to reimplement. + */ +#define LLSDParam_for(T, AS) \ +template <> \ +class LLSDParam \ +{ \ +public: \ + LLSDParam(const LLSD& value): \ + _value(value.AS()) \ + {} \ + \ + operator T() const { return _value; } \ + \ +private: \ + T _value; \ +} + +LLSDParam_for(float, asReal); +LLSDParam_for(LLUUID, asUUID); +LLSDParam_for(LLDate, asDate); +LLSDParam_for(LLURI, asURI); +LLSDParam_for(LLSD::Binary, asBinary); + /** * LLSDParam is an example of the kind of conversion you can * support with LLSDParam beyond native LLSD conversions. Normally you can't @@ -308,22 +368,4 @@ public: } }; -/** - * LLSDParam resolves conversion ambiguity. g++ considers F64, S32 and - * bool equivalent candidates for implicit conversion to float. (/me rolls eyes) - */ -template <> -class LLSDParam -{ -private: - float _value; - -public: - LLSDParam(const LLSD& value): - _value(value.asReal()) - {} - - operator float() const { return _value; } -}; - #endif // LL_LLSDUTIL_H -- cgit v1.3 From d814e76cad49d6ebeb8f34b13d5d946d9f675b76 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 3 Feb 2011 22:54:16 -0500 Subject: Introduce BOOST_FOREACH() helpers for LLSD in llsdutil.h. You can't directly write: BOOST_FOREACH(LLSD item, someLLSDarray) { ... } because LLSD has two distinct iteration mechanisms, one for arrays and one for maps, neither using the standard [const_]iterator typedefs or begin()/end() methods. But with these helpers, you can write: BOOST_FOREACH(LLSD item, llsd::inArray(someLLSDarray)) { ... } or BOOST_FOREACH(const llsd::MapEntry& pair, llsd::inMap(someLLSDmap)) { ... } These are in namespace llsd instead of being (e.g.) llsd_inMap because with a namespace at least your .cpp file can have a local 'using': using namespace llsd; BOOST_FOREACH(LLSD item, inArray(someLLSDarray)) { ... } It's namespace llsd rather than LLSD because LLSD can't be both a namespace and a class name. --- indra/llcommon/llsdutil.h | 51 +++++++++++++++++++ indra/llcommon/tests/lleventdispatcher_test.cpp | 68 ++++--------------------- 2 files changed, 62 insertions(+), 57 deletions(-) (limited to 'indra/llcommon/llsdutil.h') diff --git a/indra/llcommon/llsdutil.h b/indra/llcommon/llsdutil.h index c873b17112..65c7297cbf 100644 --- a/indra/llcommon/llsdutil.h +++ b/indra/llcommon/llsdutil.h @@ -368,4 +368,55 @@ public: } }; +namespace llsd +{ + +/***************************************************************************** +* BOOST_FOREACH() helpers for LLSD +*****************************************************************************/ +/// Usage: BOOST_FOREACH(LLSD item, inArray(someLLSDarray)) { ... } +class inArray +{ +public: + inArray(const LLSD& array): + _array(array) + {} + + typedef LLSD::array_const_iterator const_iterator; + typedef LLSD::array_iterator iterator; + + iterator begin() { return _array.beginArray(); } + iterator end() { return _array.endArray(); } + const_iterator begin() const { return _array.beginArray(); } + const_iterator end() const { return _array.endArray(); } + +private: + LLSD _array; +}; + +/// MapEntry is what you get from dereferencing an LLSD::map_[const_]iterator. +typedef std::map::value_type MapEntry; + +/// Usage: BOOST_FOREACH([const] MapEntry& e, inMap(someLLSDmap)) { ... } +class inMap +{ +public: + inMap(const LLSD& map): + _map(map) + {} + + typedef LLSD::map_const_iterator const_iterator; + typedef LLSD::map_iterator iterator; + + iterator begin() { return _map.beginMap(); } + iterator end() { return _map.endMap(); } + const_iterator begin() const { return _map.beginMap(); } + const_iterator end() const { return _map.endMap(); } + +private: + LLSD _map; +}; + +} // namespace llsd + #endif // LL_LLSDUTIL_H diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp index 2975707860..2241dea4ba 100644 --- a/indra/llcommon/tests/lleventdispatcher_test.cpp +++ b/indra/llcommon/tests/lleventdispatcher_test.cpp @@ -51,52 +51,6 @@ using std::cout; static std::ostringstream cout; #endif -/***************************************************************************** -* BOOST_FOREACH() helpers for LLSD -*****************************************************************************/ -/// Usage: BOOST_FOREACH(LLSD item, inArray(someLLSDarray)) { ... } -class inArray -{ -public: - inArray(const LLSD& array): - _array(array) - {} - - typedef LLSD::array_const_iterator const_iterator; - typedef LLSD::array_iterator iterator; - - iterator begin() { return _array.beginArray(); } - iterator end() { return _array.endArray(); } - const_iterator begin() const { return _array.beginArray(); } - const_iterator end() const { return _array.endArray(); } - -private: - LLSD _array; -}; - -/// MapEntry is what you get from dereferencing an LLSD::map_[const_]iterator. -typedef std::pair MapEntry; - -/// Usage: BOOST_FOREACH([const] MapEntry& e, inMap(someLLSDmap)) { ... } -class inMap -{ -public: - inMap(const LLSD& map): - _map(map) - {} - - typedef LLSD::map_const_iterator const_iterator; - typedef LLSD::map_iterator iterator; - - iterator begin() { return _map.beginMap(); } - iterator end() { return _map.endMap(); } - const_iterator begin() const { return _map.beginMap(); } - const_iterator end() const { return _map.endMap(); } - -private: - LLSD _map; -}; - /***************************************************************************** * Example data, functions, classes *****************************************************************************/ @@ -473,7 +427,7 @@ namespace tut (LLSDArray(paramsb)(dftb_array_full))); // std::cout << "zipped:\n" << zipped << '\n'; LLSD dft_maps_full, dft_maps_partial; - foreach(LLSD ae, inArray(zipped)) + foreach(LLSD ae, llsd::inArray(zipped)) { LLSD dft_map_full; LLSD params(ae[0]); @@ -702,7 +656,7 @@ namespace tut set_test_name("map-style registration with non-array params"); // Pass "param names" as scalar or as map LLSD attempts(LLSDArray(17)(LLSDMap("pi", 3.14)("two", 2))); - foreach(LLSD ae, inArray(attempts)) + foreach(LLSD ae, llsd::inArray(attempts)) { std::string threw; try @@ -801,7 +755,7 @@ namespace tut { set_test_name("query Callables with/out required params"); LLSD names(LLSDArray("free1")("Dmethod1")("Dcmethod1")("method1")); - foreach(LLSD ae, inArray(names)) + foreach(LLSD ae, llsd::inArray(names)) { LLSD metadata(getMetadata(ae)); ensure_equals("name mismatch", metadata["name"], ae); @@ -830,14 +784,14 @@ namespace tut (5)(LLSDArray("freena_array")("smethodna_array")("methodna_array"))) (LLSDArray (5)(LLSDArray("freenb_array")("smethodnb_array")("methodnb_array")))); - foreach(LLSD ae, inArray(expected)) + foreach(LLSD ae, llsd::inArray(expected)) { LLSD::Integer arity(ae[0].asInteger()); LLSD names(ae[1]); LLSD req(LLSD::emptyArray()); if (arity) req[arity - 1] = LLSD(); - foreach(LLSD nm, inArray(names)) + foreach(LLSD nm, llsd::inArray(names)) { LLSD metadata(getMetadata(nm)); ensure_equals("name mismatch", metadata["name"], nm); @@ -856,7 +810,7 @@ namespace tut // - (Free function | non-static method), map style, no params (ergo // no defaults) LLSD names(LLSDArray("free0_map")("smethod0_map")("method0_map")); - foreach(LLSD nm, inArray(names)) + foreach(LLSD nm, llsd::inArray(names)) { LLSD metadata(getMetadata(nm)); ensure_equals("name mismatch", metadata["name"], nm); @@ -886,7 +840,7 @@ namespace tut (LLSDArray("smethodnb_map_adft")("smethodnb_map_mdft")) (LLSDArray("methodna_map_adft")("methodna_map_mdft")) (LLSDArray("methodnb_map_adft")("methodnb_map_mdft"))); - foreach(LLSD eq, inArray(equivalences)) + foreach(LLSD eq, llsd::inArray(equivalences)) { LLSD adft(eq[0]); LLSD mdft(eq[1]); @@ -943,11 +897,11 @@ namespace tut // Generate maps containing parameter names not provided by the // dft[ab]_map_partial maps. LLSD skipreqa(allreqa), skipreqb(allreqb); - foreach(const MapEntry& me, inMap(dfta_map_partial)) + foreach(const llsd::MapEntry& me, llsd::inMap(dfta_map_partial)) { skipreqa.erase(me.first); } - foreach(const MapEntry& me, inMap(dftb_map_partial)) + foreach(const llsd::MapEntry& me, llsd::inMap(dftb_map_partial)) { skipreqb.erase(me.first); } @@ -989,7 +943,7 @@ namespace tut (LLSDArray("freenb_map_mdft")("smethodnb_map_mdft")("methodnb_map_mdft")) (LLSDArray(LLSD::emptyMap())(dftb_map_full)))); // required, optional - foreach(LLSD grp, inArray(groups)) + foreach(LLSD grp, llsd::inArray(groups)) { // Internal structure of each group in 'groups': LLSD names(grp[0]); @@ -998,7 +952,7 @@ namespace tut cout << "For " << names << ",\n" << "required:\n" << required << "\noptional:\n" << optional << std::endl; // Loop through 'names' - foreach(LLSD nm, inArray(names)) + foreach(LLSD nm, llsd::inArray(names)) { LLSD metadata(getMetadata(nm)); ensure_equals("name mismatch", metadata["name"], nm); -- cgit v1.3