summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNat Goodspeed <nat@lindenlab.com>2009-05-26 22:36:38 +0000
committerNat Goodspeed <nat@lindenlab.com>2009-05-26 22:36:38 +0000
commita81c084debb4075f36bacd59cbe332c2f0548d50 (patch)
tree2608e6f40aaf2323ffc961d306b1e3b027732d71
parent01d390825a5d9ba37715b80cd0aa7aede022dcec (diff)
Add llsd_equals(), a function whose absence sorely puzzles me
-rw-r--r--indra/llcommon/llsdutil.cpp92
-rw-r--r--indra/llcommon/llsdutil.h3
-rw-r--r--indra/test/llsdutil_tut.cpp58
3 files changed, 151 insertions, 2 deletions
diff --git a/indra/llcommon/llsdutil.cpp b/indra/llcommon/llsdutil.cpp
index 643720cebe..c8d8030e87 100644
--- a/indra/llcommon/llsdutil.cpp
+++ b/indra/llcommon/llsdutil.cpp
@@ -576,3 +576,95 @@ std::string llsd_matches(const LLSD& prototype, const LLSD& data, const std::str
// bad LLSD doesn't define isConvertible(Type to, Type from).
return match_types(prototype.type(), TypeVector(), data.type(), pfx);
}
+
+bool llsd_equals(const LLSD& lhs, const LLSD& rhs)
+{
+ // We're comparing strict equality of LLSD representation rather than
+ // performing any conversions. So if the types aren't equal, the LLSD
+ // values aren't equal.
+ if (lhs.type() != rhs.type())
+ {
+ return false;
+ }
+
+ // Here we know both types are equal. Now compare values.
+ switch (lhs.type())
+ {
+ case LLSD::TypeUndefined:
+ // Both are TypeUndefined. There's nothing more to know.
+ return true;
+
+#define COMPARE_SCALAR(type) \
+ case LLSD::Type##type: \
+ /* LLSD::URI has operator!=() but not operator==() */ \
+ /* rely on the optimizer for all others */ \
+ return (! (lhs.as##type() != rhs.as##type()))
+
+ 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);
+ COMPARE_SCALAR(URI);
+ COMPARE_SCALAR(Binary);
+
+#undef COMPARE_SCALAR
+
+ case LLSD::TypeArray:
+ {
+ LLSD::array_const_iterator
+ lai(lhs.beginArray()), laend(lhs.endArray()),
+ rai(rhs.beginArray()), raend(rhs.endArray());
+ // Compare array elements, walking the two arrays in parallel.
+ for ( ; lai != laend && rai != raend; ++lai, ++rai)
+ {
+ // If any one array element is unequal, the arrays are unequal.
+ if (! llsd_equals(*lai, *rai))
+ return false;
+ }
+ // Here we've reached the end of one or the other array. They're equal
+ // only if they're BOTH at end: that is, if they have equal length too.
+ return (lai == laend && rai == raend);
+ }
+
+ case LLSD::TypeMap:
+ {
+ // Build a set of all rhs keys.
+ std::set<LLSD::String> rhskeys;
+ for (LLSD::map_const_iterator rmi(rhs.beginMap()), rmend(rhs.endMap());
+ rmi != rmend; ++rmi)
+ {
+ rhskeys.insert(rmi->first);
+ }
+ // Now walk all the lhs keys.
+ for (LLSD::map_const_iterator lmi(lhs.beginMap()), lmend(lhs.endMap());
+ lmi != lmend; ++lmi)
+ {
+ // Try to erase this lhs key from the set of rhs keys. If rhs has
+ // no such key, the maps are unequal. erase(key) returns count of
+ // items erased.
+ if (rhskeys.erase(lmi->first) != 1)
+ return false;
+ // Both maps have the current key. Compare values.
+ if (! llsd_equals(lmi->second, rhs[lmi->first]))
+ return false;
+ }
+ // We've now established that all the lhs keys have equal values in
+ // both maps. The maps are equal unless rhs contains a superset of
+ // those keys.
+ return rhskeys.empty();
+ }
+
+ default:
+ // 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 << "): "
+ "unknown type " << lhs.type() << LL_ENDL;
+ return false; // pacify the compiler
+ }
+}
diff --git a/indra/llcommon/llsdutil.h b/indra/llcommon/llsdutil.h
index a4175be450..31d219da52 100644
--- a/indra/llcommon/llsdutil.h
+++ b/indra/llcommon/llsdutil.h
@@ -129,6 +129,9 @@ 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
+bool llsd_equals(const LLSD& lhs, const LLSD& rhs);
+
// Simple function to copy data out of input & output iterators if
// there is no need for casting.
template<typename Input> LLSD llsd_copy_array(Input iter, Input end)
diff --git a/indra/test/llsdutil_tut.cpp b/indra/test/llsdutil_tut.cpp
index 35ab80e791..d125bb0005 100644
--- a/indra/test/llsdutil_tut.cpp
+++ b/indra/test/llsdutil_tut.cpp
@@ -45,6 +45,7 @@
#include "llquaternion.h"
#include "llsdutil.h"
#include "llsdutil_math.h"
+#include "stringize.h"
#include <set>
#include <boost/range.hpp>
@@ -207,14 +208,16 @@ namespace tut
map.insert("URI", LLSD::URI());
map.insert("Binary", LLSD::Binary());
map.insert("Map", LLSD().insert("foo", LLSD()));
- // array can't be constructed on the fly
+ // Only an empty array can be constructed on the fly
LLSD array;
array.append(LLSD());
map.insert("Array", array);
// These iterators are declared outside our various for loops to avoid
// fatal MSVC warning: "I used to be broken, but I'm all better now!"
- LLSD::map_const_iterator mi(map.beginMap()), mend(map.endMap());
+ LLSD::map_const_iterator mi, mend(map.endMap());
+
+ /*-------------------------- llsd_matches --------------------------*/
// empty prototype matches anything
for (mi = map.beginMap(); mi != mend; ++mi)
@@ -337,5 +340,56 @@ namespace tut
static const char* matches[] = { "Binary" };
test_matches("Binary", map, boost::begin(matches), boost::end(matches));
}
+
+ /*-------------------------- llsd_equals ---------------------------*/
+
+ // Cross-product of each LLSD type with every other
+ for (LLSD::map_const_iterator lmi(map.beginMap()), lmend(map.endMap());
+ lmi != lmend; ++lmi)
+ {
+ for (LLSD::map_const_iterator rmi(map.beginMap()), rmend(map.endMap());
+ rmi != rmend; ++rmi)
+ {
+ // Name this test based on the map keys naming the types of
+ // interest, e.g "String::Integer".
+ // We expect the values (xmi->second) to be equal if and only
+ // if the type names (xmi->first) are equal.
+ ensure(STRINGIZE(lmi->first << "::" << rmi->first),
+ bool(lmi->first == rmi->first) ==
+ bool(llsd_equals(lmi->second, rmi->second)));
+ }
+ }
+
+ // Array cases
+ LLSD rarray;
+ rarray.append(1.0);
+ rarray.append(2);
+ rarray.append("3");
+ LLSD larray(rarray);
+ ensure("llsd_equals(equal arrays)", llsd_equals(larray, rarray));
+ rarray[2] = "4";
+ ensure("llsd_equals(different [2])", ! llsd_equals(larray, rarray));
+ rarray = larray;
+ rarray.append(LLSD::Date());
+ ensure("llsd_equals(longer right array)", ! llsd_equals(larray, rarray));
+ rarray = larray;
+ rarray.erase(2);
+ ensure("llsd_equals(shorter right array)", ! llsd_equals(larray, rarray));
+
+ // Map cases
+ LLSD rmap;
+ rmap["San Francisco"] = 65;
+ rmap["Phoenix"] = 92;
+ rmap["Boston"] = 77;
+ LLSD lmap(rmap);
+ ensure("llsd_equals(equal maps)", llsd_equals(lmap, rmap));
+ rmap["Boston"] = 80;
+ ensure("llsd_equals(different [\"Boston\"])", ! llsd_equals(lmap, rmap));
+ rmap = lmap;
+ rmap["Atlanta"] = 95;
+ ensure("llsd_equals(superset right map)", ! llsd_equals(lmap, rmap));
+ rmap = lmap;
+ lmap["Seattle"] = 72;
+ ensure("llsd_equals(superset left map)", ! llsd_equals(lmap, rmap));
}
}