Expand the list of types for attribute_value's variant,

...allowing the processing overhead to be localized to the assignment
operators.

Really fixes bug #19201 (the reported example is still/again broken in
2012-12-13T03:24:23Z!jt_coding@verizon.net).  

Related bugs that I verified have not been reintroduced: #19681,
#19690, #19702.
This commit is contained in:
J. Tyne 2012-12-14 17:41:19 +00:00
parent f0222685ae
commit b58d88c84a
4 changed files with 240 additions and 92 deletions

View file

@ -83,6 +83,13 @@ struct tconfig_implementation
/* ** Attribute value implementation ** */
// Special string values.
const std::string config::attribute_value::s_yes("yes");
const std::string config::attribute_value::s_no("no");
const std::string config::attribute_value::s_true("true");
const std::string config::attribute_value::s_false("false");
/** Default implementation, but defined out-of-line for efficiency reasons. */
config::attribute_value::attribute_value()
: value_()
@ -109,44 +116,129 @@ config::attribute_value &config::attribute_value::operator=(const config::attrib
config::attribute_value &config::attribute_value::operator=(bool v)
{
value_ = v;
value_ = yes_no(v);
return *this;
}
config::attribute_value &config::attribute_value::operator=(int v)
{
value_ = double(v);
value_ = v;
return *this;
}
config::attribute_value &config::attribute_value::operator=(long v)
config::attribute_value &config::attribute_value::operator=(long long v)
{
value_ = double(v);
if ( v > 0 )
// We can store this unsigned.
return *this = static_cast<unsigned long long>(v);
if ( v >= INT_MIN )
// We can store this as an int.
return *this = static_cast<int>(v);
// Getting to this point should be rare. (Currently, geting here means
// something like there was so much draining in a campaign that the
// total damage taken is not only negative, but so negative that an
// int cannot hold the value.) So rare that it is not worth precise
// treatment; just use a double.
value_ = static_cast<double>(v);
return *this;
}
config::attribute_value &config::attribute_value::operator=(unsigned long long v)
{
value_ = double(v);
// Use int for smaller numbers.
if ( v <= INT_MAX )
return *this = static_cast<int>(v);
value_ = v;
return *this;
}
config::attribute_value &config::attribute_value::operator=(double v)
{
// Try to store integers in other types.
if ( v > 0.0 ) {
// Convert to unsigned and pass this off to that assignment operator.
unsigned long long ull = static_cast<unsigned long long>(v);
if ( static_cast<double>(ull) == v )
return *this = ull;
}
else {
// Convert to integer and pass this off to that assignment operator.
int i = static_cast<int>(v);
if ( static_cast<double>(i) == v )
return *this = i;
}
// If we get here, this does in fact get stored as a double.
value_ = v;
return *this;
}
namespace {
/**
* Attempts to convert @a source to the template type.
* This is to avoid "overzealous reinterpretation of certain WML strings as
* numeric tpyes" (c.f. bug #19201).
* @returns true if the conversion was successful and the source string
* can be reobtained by streaming the result.
*/
template<typename To>
bool from_string_verify(const std::string & source, To & res)
{
// Check 1: convertable to the target type.
std::istringstream in_str(source);
if ( !(in_str >> res) )
return false;
// Check 2: convertable back to the same string.
std::ostringstream out_str;
out_str << res;
return out_str.str() == source;
}
}
config::attribute_value &config::attribute_value::operator=(const std::string &v)
{
// Handle some special strings.
if (v.empty()) { value_ = v; return *this; }
if (v == "yes" || v == "true") return *this = true;
if (v == "no" || v == "false") return *this = false;
if ( v == s_yes ) { value_ = yes_no(true); return *this; }
if ( v == s_no ) { value_ = yes_no(false); return *this; }
if ( v == s_true ) { value_ = true_false(true); return *this; }
if ( v == s_false ) { value_ = true_false(false); return *this; }
// Attempt to convert to a number.
char *eptr;
int i = strtol(v.c_str(), &eptr, 0);
if (*eptr == '\0') return *this = i;
double d = strtod(v.c_str(), &eptr);
if (*eptr == '\0') return *this = d;
if ( *eptr == '\0' ) {
// Possibly a number. See what type it should be stored in.
// (All conversions will be from the string since the largest integer
// type could have more precision than a double.)
if ( d > 0.0 ) {
// The largest type for positive integers is unsigned long long.
unsigned long long ull = 0;
if ( from_string_verify<unsigned long long>(v, ull) )
return *this = ull;
}
else {
// The largest (variant) type for negative integers is int.
int i = 0;
if ( from_string_verify<int>(v, i) )
return *this = i;
}
// This does not look like an integer, so it should be a double.
// However, make sure it can convert back to the same string (in
// case this is a string that just looks like a numeric value).
std::ostringstream tester;
tester << d;
if ( tester.str() == v ) {
value_ = d;
return *this;
}
}
// No conversion possible. Store the string.
value_ = v;
return *this;
}
@ -161,97 +253,85 @@ config::attribute_value &config::attribute_value::operator=(const t_string &v)
bool config::attribute_value::to_bool(bool def) const
{
if (const bool *p = boost::get<const bool>(&value_)) return *p;
if ( const yes_no *p = boost::get<const yes_no>(&value_) )
return *p;
if ( const true_false *p = boost::get<const true_false>(&value_) )
return *p;
// No other types are ever recognized as boolean.
return def;
}
namespace {
/// Visitor for converting a variant to a numeric type (T).
template <typename T>
class attribute_numeric_visitor : public boost::static_visitor<T>
{
public:
// Constructor stores the default value.
attribute_numeric_visitor(T def) : def_(def) {}
T operator()(boost::blank const &) const { return def_; }
T operator()(bool) const { return def_; }
T operator()(int i) const { return static_cast<T>(i); }
T operator()(unsigned long long u) const { return static_cast<T>(u); }
T operator()(double d) const { return static_cast<T>(d); }
T operator()(std::string const &s) const { return lexical_cast_default<T>(s, def_); }
T operator()(t_string const &) const { return def_; }
private:
const T def_;
};
}
int config::attribute_value::to_int(int def) const
{
const double* i = boost::get<const double>(&value_);
if(i != NULL)
{
return int(*i);
}
return def;
}
unsigned config::attribute_value::to_unsigned(unsigned def) const
{
const double* i = boost::get<const double>(&value_);
if(i != NULL)
{
return unsigned(*i);
}
return def;
return apply_visitor(attribute_numeric_visitor<int>(def));
}
long long config::attribute_value::to_long_long(long long def) const
{
const double* i = boost::get<const double>(&value_);
if (i != NULL)
{
return static_cast<long long>(*i);
}
return def;
return apply_visitor(attribute_numeric_visitor<long long>(def));
}
unsigned config::attribute_value::to_unsigned(unsigned def) const
{
return apply_visitor(attribute_numeric_visitor<unsigned>(def));
}
size_t config::attribute_value::to_size_t(size_t def) const
{
const double* i = boost::get<const double>(&value_);
if (i != NULL)
{
return static_cast<size_t>(*i);
}
return def;
return apply_visitor(attribute_numeric_visitor<size_t>(def));
}
time_t config::attribute_value::to_time_t(time_t def) const
{
const double* i = boost::get<const double>(&value_);
if (i != NULL)
{
return static_cast<time_t>(*i);
}
return def;
return apply_visitor(attribute_numeric_visitor<time_t>(def));
}
double config::attribute_value::to_double(double def) const
{
const double* d = boost::get<const double>(&value_);
if(d != NULL)
{
return *d;
}
return def;
return apply_visitor(attribute_numeric_visitor<double>(def));
}
struct config_attribute_str_visitor : boost::static_visitor<std::string>
/// Visitor for converting a variant to a string.
class config::attribute_value::string_visitor
: public boost::static_visitor<std::string>
{
std::string operator()(boost::blank const &) const
{ return std::string(); }
std::string operator()(bool b) const
{
static std::string s_yes("yes"), s_no("no");
return b ? s_yes : s_no;
}
std::string operator()(double d) const
{
long i = static_cast<long>(d);
if (static_cast<double>(i) == d)
return str_cast(i);
else
return str_cast(d);
}
std::string operator()(std::string const &s) const
{ return s; }
std::string operator()(t_string const &s) const
{ return s.str(); }
public:
std::string operator()(const boost::blank &) const { return std::string(); }
std::string operator()(const yes_no & b) const { return b.str(); }
std::string operator()(const true_false & b) const { return b.str(); }
std::string operator()(int i) const { return str_cast(i); }
std::string operator()(unsigned long long u) const { return str_cast(u); }
std::string operator()(double d) const { return str_cast(d); }
std::string operator()(std::string const &s) const { return s; }
std::string operator()(t_string const &s) const { return s.str(); }
};
std::string config::attribute_value::str() const
{
return boost::apply_visitor(config_attribute_str_visitor(), value_);
return apply_visitor(string_visitor());
}
t_string config::attribute_value::t_str() const
@ -260,11 +340,17 @@ t_string config::attribute_value::t_str() const
return str();
}
/**
* Tests for an attribute that was never set.
*/
bool config::attribute_value::blank() const
{
return boost::get<const boost::blank>(&value_);
}
/**
* Tests for an attribute that either was never set or was set to "".
*/
bool config::attribute_value::empty() const
{
if (boost::get<const boost::blank>(&value_)) return true;
@ -273,6 +359,11 @@ bool config::attribute_value::empty() const
}
/**
* Checks for equality of the attribute values when viewed as strings.
* One exception: blanks only equal other blanks, even though their string
* representation is "".
*/
bool config::attribute_value::operator==(const config::attribute_value &other) const
{
return value_ == other.value_;
@ -280,7 +371,9 @@ bool config::attribute_value::operator==(const config::attribute_value &other) c
std::ostream &operator<<(std::ostream &os, const config::attribute_value &v)
{
return os << v.str();
// Simple implementation, but defined out-of-line because of the templating
// involved.
return os << v.value_;
}

View file

@ -177,7 +177,55 @@ public:
*/
class attribute_value
{
typedef boost::variant<boost::blank, bool, double, std::string, t_string> value_type;
/// A wrapper for bool to get the correct streaming ("true"/"false").
/// Most visitors can simply treat this as bool.
class true_false
{
bool value_;
public:
explicit true_false(bool value = false) : value_(value) {}
operator bool() const { return value_; }
const std::string & str() const
{ return value_ ? config::attribute_value::s_true :
config::attribute_value::s_false; }
};
friend std::ostream& operator<<(std::ostream &os, const true_false &v);
/// A wrapper for bool to get the correct streaming ("yes"/"no").
/// Most visitors can simply treat this as bool.
class yes_no
{
bool value_;
public:
explicit yes_no(bool value = false) : value_(value) {}
operator bool() const { return value_; }
const std::string & str() const
{ return value_ ? config::attribute_value::s_yes :
config::attribute_value::s_no; }
};
friend std::ostream& operator<<(std::ostream &os, const yes_no &v);
/// Visitor for converting a variant to a string.
class string_visitor;
// Data will be stored in a variant, allowing for the possibility of
// boolean, numeric, and translatable data in addition to basic string
// data. For most purposes, int is the preferred type for numeric data
// as it is fast (often natural word size). While it is desirable to
// use few types (to keep the overhead low), we do have use cases for
// fractions (double) and huge numbers (up to the larger of LLONG_MAX
// and SIZE_MAX).
typedef boost::variant<boost::blank,
true_false, yes_no,
int, unsigned long long, double,
std::string, t_string
> value_type;
/// The stored value will always use the first type from the variant
/// definition that can represent it and that can be streamed to the
/// correct string representation (if applicable).
/// This is enforced upon assignment.
value_type value_;
public:
@ -193,10 +241,10 @@ public:
// Numeric assignments:
attribute_value &operator=(bool v);
attribute_value &operator=(int v);
attribute_value &operator=(long v);
attribute_value &operator=(long long v) { return operator=(double(v)); }
attribute_value &operator=(unsigned v) { return operator=(double(v)); }
attribute_value &operator=(unsigned long v) { return operator=(double(v)); }
attribute_value &operator=(long v) { return operator=(static_cast<long long>(v)); }
attribute_value &operator=(long long v);
attribute_value &operator=(unsigned v) { return operator=(static_cast<unsigned long long>(v)); }
attribute_value &operator=(unsigned long v) { return operator=(static_cast<unsigned long long>(v)); }
attribute_value &operator=(unsigned long long v);
attribute_value &operator=(double v);
@ -208,8 +256,8 @@ public:
// Extracting as a specific type:
bool to_bool(bool def = false) const;
int to_int(int def = 0) const;
unsigned to_unsigned(unsigned def = 0) const;
long long to_long_long(long long def = 0) const;
unsigned to_unsigned(unsigned def = 0) const;
size_t to_size_t(size_t def = 0) const;
time_t to_time_t(time_t def = 0) const;
double to_double(double def = 0.) const;
@ -241,6 +289,10 @@ public:
template <typename V>
typename V::result_type apply_visitor(const V & visitor) const
{ return boost::apply_visitor(visitor, value_); }
// Special strings.
static const std::string s_yes, s_no;
static const std::string s_true, s_false;
};
typedef std::map<std::string, attribute_value> attribute_map;
@ -572,4 +624,7 @@ public:
virtual config::attribute_value get_variable_const(const std::string &id) const = 0;
};
inline std::ostream &operator<<(std::ostream &os, const config::attribute_value::true_false &v) { return os << v.str(); }
inline std::ostream &operator<<(std::ostream &os, const config::attribute_value::yes_no &v) { return os << v.str(); }
#endif

View file

@ -178,18 +178,17 @@ namespace {
{
lua_State *L;
luaW_pushscalar_visitor(lua_State *l): L(l) {}
void operator()(boost::blank const &) const
{ lua_pushnil(L); }
void operator()(bool b) const
{ lua_pushboolean(L, b); }
void operator()(int i) const
{ lua_pushinteger(L, i); }
void operator()(unsigned long long ull) const
{ lua_pushnumber(L, ull); }
void operator()(double d) const
{ lua_pushnumber(L, d); }
void operator()(size_t s) const
{ lua_pushnumber(L,s); }
void operator()(long t) const
{ lua_pushnumber(L,t); }
void operator()(int i) const
{ lua_pushnumber(L,i); }
void operator()(std::string const &s) const
{ lua_pushstring(L, s.c_str()); }
void operator()(t_string const &s) const

View file

@ -441,25 +441,26 @@ namespace { // helpers for write_key_val().
return escaped_string(value.begin(), value.end());
}
struct write_key_val_visitor : boost::static_visitor<void>
class write_key_val_visitor : public boost::static_visitor<void>
{
std::ostream &out_;
const unsigned level_;
std::string &textdomain_;
const std::string &key_;
public:
write_key_val_visitor(std::ostream &out, unsigned level,
std::string &textdomain, const std::string &key)
: out_(out), level_(level), textdomain_(textdomain), key_(key)
{}
// Generic visitor just streams "key=value".
template <typename T> void operator()(T const & v) const
{ indent(); out_ << key_ << '=' << v; }
// Specialized visitors for things that go in quotes:
void operator()(boost::blank const &) const
{ indent(); out_ << key_ << '=' << "\"\""; }
void operator()(bool b) const
{ indent(); out_ << key_ << '=' << (b ? "yes" : "no"); }
void operator()(double d) const
{ indent(); out_ << key_ << '=';
int i = d; if (d == i) out_ << i; else out_ << d; }
void operator()(std::string const &s) const
{ indent(); out_ << key_ << '=' << '"' << escaped_string(s) << '"'; }
void operator()(t_string const &s) const;