move config::attribte_value out of config.?pp
this has 2 advantages: 1) we can now forwaed declare the class config_attribute_value without including config.hpp 2) the config.?pp files are a little easier to read.
This commit is contained in:
parent
d06e9c30b2
commit
13b00f73ff
5 changed files with 643 additions and 498 deletions
|
@ -1,6 +1,7 @@
|
|||
color.cpp
|
||||
color_range.cpp
|
||||
config.cpp
|
||||
config_attribute_value.cpp
|
||||
filesystem_boost.cpp
|
||||
filesystem_common.cpp
|
||||
font/constants.cpp
|
||||
|
|
334
src/config.cpp
334
src/config.cpp
|
@ -107,340 +107,6 @@ struct config_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_()
|
||||
{
|
||||
}
|
||||
|
||||
/** Default implementation, but defined out-of-line for efficiency reasons. */
|
||||
config::attribute_value::~attribute_value()
|
||||
{
|
||||
}
|
||||
|
||||
/** Default implementation, but defined out-of-line for efficiency reasons. */
|
||||
config::attribute_value::attribute_value(const config::attribute_value &that)
|
||||
: value_(that.value_)
|
||||
{
|
||||
}
|
||||
|
||||
/** Default implementation, but defined out-of-line for efficiency reasons. */
|
||||
config::attribute_value &config::attribute_value::operator=(const config::attribute_value &that)
|
||||
{
|
||||
value_ = that.value_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
config::attribute_value &config::attribute_value::operator=(bool v)
|
||||
{
|
||||
value_ = yes_no(v);
|
||||
return *this;
|
||||
}
|
||||
|
||||
config::attribute_value &config::attribute_value::operator=(int v)
|
||||
{
|
||||
value_ = v;
|
||||
return *this;
|
||||
}
|
||||
|
||||
config::attribute_value &config::attribute_value::operator=(long long 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, getting 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)
|
||||
{
|
||||
// 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 reinterpretations of certain WML strings as
|
||||
* numeric types" (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 == 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;
|
||||
double d = strtod(v.c_str(), &eptr);
|
||||
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;
|
||||
}
|
||||
|
||||
config::attribute_value &config::attribute_value::operator=(const t_string &v)
|
||||
{
|
||||
if (!v.translatable()) return *this = v.str();
|
||||
value_ = v;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
bool config::attribute_value::to_bool(bool def) const
|
||||
{
|
||||
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()(const std::string& 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
|
||||
{
|
||||
return apply_visitor(attribute_numeric_visitor<int>(def));
|
||||
}
|
||||
|
||||
long long config::attribute_value::to_long_long(long long def) const
|
||||
{
|
||||
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
|
||||
{
|
||||
return apply_visitor(attribute_numeric_visitor<size_t>(def));
|
||||
}
|
||||
|
||||
time_t config::attribute_value::to_time_t(time_t def) const
|
||||
{
|
||||
return apply_visitor(attribute_numeric_visitor<time_t>(def));
|
||||
}
|
||||
|
||||
double config::attribute_value::to_double(double def) const
|
||||
{
|
||||
return apply_visitor(attribute_numeric_visitor<double>(def));
|
||||
}
|
||||
|
||||
/// Visitor for converting a variant to a string.
|
||||
class config::attribute_value::string_visitor
|
||||
: public boost::static_visitor<std::string>
|
||||
{
|
||||
const std::string default_;
|
||||
public:
|
||||
string_visitor(const std::string& fallback) : default_(fallback) {}
|
||||
std::string operator()(const boost::blank &) const { return default_; }
|
||||
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 lexical_cast<std::string>(i); }
|
||||
std::string operator()(unsigned long long u) const { return lexical_cast<std::string>(u); }
|
||||
std::string operator()(double d) const { return lexical_cast<std::string>(d); }
|
||||
std::string operator()(const std::string& s) const { return s; }
|
||||
std::string operator()(t_string const &s) const { return s.str(); }
|
||||
};
|
||||
|
||||
std::string config::attribute_value::str(const std::string& fallback) const
|
||||
{
|
||||
return apply_visitor(string_visitor(fallback));
|
||||
}
|
||||
|
||||
t_string config::attribute_value::t_str() const
|
||||
{
|
||||
if (const t_string *p = boost::get<const t_string>(&value_)) return *p;
|
||||
return str();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for an attribute that was never set.
|
||||
*/
|
||||
bool config::attribute_value::blank() const
|
||||
{
|
||||
return boost::get<const boost::blank>(&value_) != nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
if (const std::string *p = boost::get<const std::string>(&value_)) return p->empty();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/// Visitor handling equality checks.
|
||||
class config::attribute_value::equality_visitor
|
||||
: public boost::static_visitor<bool>
|
||||
{
|
||||
public:
|
||||
// Most generic: not equal.
|
||||
template <typename T, typename U>
|
||||
bool operator()(const T &, const U &) const { return false; }
|
||||
|
||||
// Same types are comparable and might be equal.
|
||||
template <typename T>
|
||||
bool operator()(const T & lhs, const T & rhs) const { return lhs == rhs; }
|
||||
|
||||
// Boolean values can be compared.
|
||||
bool operator()(const true_false & lhs, const yes_no & rhs) const { return bool(lhs) == bool(rhs); }
|
||||
bool operator()(const yes_no & lhs, const true_false & rhs) const { return bool(lhs) == bool(rhs); }
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks for equality of the attribute values when viewed as strings.
|
||||
* Exception: Boolean synonyms can be equal ("yes" == "true").
|
||||
* Note: Blanks have no string representation, so do not equal "" (an empty string).
|
||||
*/
|
||||
bool config::attribute_value::operator==(const config::attribute_value &other) const
|
||||
{
|
||||
return boost::apply_visitor(equality_visitor(), value_, other.value_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for equality of the attribute values when viewed as strings.
|
||||
* Exception: Boolean synonyms can be equal ("yes" == "true").
|
||||
* Note: Blanks have no string representation, so do not equal "" (an empty string).
|
||||
* Also note that translatable string are never equal to non translatable strings.
|
||||
*/
|
||||
bool config::attribute_value::equals(const std::string &str) const
|
||||
{
|
||||
attribute_value v;
|
||||
v = str;
|
||||
return *this == v;
|
||||
// if c["a"] = "1" then this solution would have resulted in c["a"] == "1" beeing false
|
||||
// because a["a"] is '1' and not '"1"'.
|
||||
// return boost::apply_visitor(std::bind( equality_visitor(), _1, std::cref(str) ), value_);
|
||||
// that's why we don't use it.
|
||||
}
|
||||
|
||||
std::ostream &operator<<(std::ostream &os, const config::attribute_value &v)
|
||||
{
|
||||
// Simple implementation, but defined out-of-line because of the templating
|
||||
// involved.
|
||||
return os << v.value_;
|
||||
}
|
||||
|
||||
|
||||
/* ** config implementation ** */
|
||||
|
||||
|
||||
|
|
166
src/config.hpp
166
src/config.hpp
|
@ -45,6 +45,7 @@
|
|||
#include <boost/variant/variant.hpp>
|
||||
#include <boost/range/iterator_range.hpp>
|
||||
|
||||
#include "config_attribute_value.hpp"
|
||||
#include "exceptions.hpp"
|
||||
#include "tstring.hpp"
|
||||
|
||||
|
@ -232,168 +233,7 @@ public:
|
|||
* @note The blank variant is only used when querying missing attributes.
|
||||
* It is not stored in config objects.
|
||||
*/
|
||||
class attribute_value
|
||||
{
|
||||
/// A wrapper for bool to get the correct streaming ("true"/"false").
|
||||
/// Most visitors can simply treat this as bool.
|
||||
public:
|
||||
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);
|
||||
private:
|
||||
/// Visitor for checking equality.
|
||||
class equality_visitor;
|
||||
/// 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:
|
||||
/// Default implementation, but defined out-of-line for efficiency reasons.
|
||||
attribute_value();
|
||||
/// Default implementation, but defined out-of-line for efficiency reasons.
|
||||
~attribute_value();
|
||||
/// Default implementation, but defined out-of-line for efficiency reasons.
|
||||
attribute_value(const attribute_value &);
|
||||
/// Default implementation, but defined out-of-line for efficiency reasons.
|
||||
attribute_value &operator=(const attribute_value &);
|
||||
|
||||
// Numeric assignments:
|
||||
attribute_value &operator=(bool v);
|
||||
attribute_value &operator=(int 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);
|
||||
|
||||
// String assignments:
|
||||
attribute_value &operator=(const char *v) { return operator=(std::string(v)); }
|
||||
attribute_value &operator=(const std::string &v);
|
||||
attribute_value &operator=(const t_string &v);
|
||||
template<typename T>
|
||||
typename std::enable_if<std::is_base_of<enum_tag, T>::value, attribute_value &>::type operator=(const T &v)
|
||||
{
|
||||
return operator=(T::enum_to_string(v));
|
||||
}
|
||||
// Extracting as a specific type:
|
||||
bool to_bool(bool def = false) const;
|
||||
int to_int(int 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;
|
||||
std::string str(const std::string& fallback = "") const;
|
||||
t_string t_str() const;
|
||||
/**
|
||||
@param T a type created with MAKE_ENUM macro
|
||||
NOTE: since T::VALUE constants is not of type T but of the underlying enum type you must specify the template parameter explicitly
|
||||
TODO: Fix this in c++11 using constexpr types.
|
||||
*/
|
||||
template<typename T>
|
||||
typename std::enable_if<std::is_base_of<enum_tag, T>::value, T>::type to_enum(const T &v) const
|
||||
{
|
||||
return T::string_to_enum(this->str(), v);
|
||||
}
|
||||
|
||||
// Implicit conversions:
|
||||
operator int() const { return to_int(); }
|
||||
operator std::string() const { return str(); }
|
||||
operator t_string() const { return t_str(); }
|
||||
|
||||
/// Tests for an attribute that was never set.
|
||||
bool blank() const;
|
||||
/// Tests for an attribute that either was never set or was set to "".
|
||||
bool empty() const;
|
||||
|
||||
|
||||
// Comparisons:
|
||||
bool operator==(const attribute_value &other) const;
|
||||
bool operator!=(const attribute_value &other) const
|
||||
{ return !operator==(other); }
|
||||
|
||||
bool equals(const std::string& str) const;
|
||||
// These function prevent t_string creation in case of c["a"] == "b" comparisons.
|
||||
// The templates are needed to prevent using these function in case of c["a"] == 0 comparisons.
|
||||
template<typename T>
|
||||
typename std::enable_if<std::is_same<const std::string, typename std::add_const<T>::type>::value, bool>::type
|
||||
friend operator==(const attribute_value &val, const T &str)
|
||||
{ return val.equals(str); }
|
||||
|
||||
template<typename T>
|
||||
typename std::enable_if<std::is_same<const char*, T>::value, bool>::type
|
||||
friend operator==(const attribute_value &val, T str)
|
||||
{ return val.equals(std::string(str)); }
|
||||
|
||||
template<typename T>
|
||||
bool friend operator==(const T &str, const attribute_value &val)
|
||||
{ return val == str; }
|
||||
|
||||
template<typename T>
|
||||
bool friend operator!=(const attribute_value &val, const T &str)
|
||||
{ return !(val == str); }
|
||||
|
||||
template<typename T>
|
||||
bool friend operator!=(const T &str, const attribute_value &val)
|
||||
{ return !(val == str); }
|
||||
|
||||
// Streaming:
|
||||
friend std::ostream& operator<<(std::ostream &os, const attribute_value &v);
|
||||
|
||||
// Visitor support:
|
||||
/// Applies a visitor to the underlying variant.
|
||||
/// (See the documentation for Boost.Variant.)
|
||||
template <typename V>
|
||||
typename V::result_type apply_visitor(const V & visitor) const
|
||||
{ return boost::apply_visitor(visitor, value_); }
|
||||
|
||||
private:
|
||||
// Special strings.
|
||||
static const std::string s_yes, s_no;
|
||||
static const std::string s_true, s_false;
|
||||
};
|
||||
using attribute_value = config_attribute_value;
|
||||
|
||||
typedef std::map<
|
||||
std::string
|
||||
|
@ -911,5 +751,3 @@ 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(); }
|
||||
|
|
371
src/config_attribute_value.cpp
Normal file
371
src/config_attribute_value.cpp
Normal file
|
@ -0,0 +1,371 @@
|
|||
/*
|
||||
Copyright (C) 2003 by David White <dave@whitevine.net>
|
||||
Copyright (C) 2005 - 2017 by Guillaume Melquiond <guillaume.melquiond@gmail.com>
|
||||
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Routines related to configuration-files / WML.
|
||||
*/
|
||||
|
||||
#include "config_attribute_value.hpp"
|
||||
|
||||
#include "lexical_cast.hpp"
|
||||
#include "log.hpp"
|
||||
#include "utils/const_clone.hpp"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <deque>
|
||||
#include <istream>
|
||||
|
||||
#include "utils/functional.hpp"
|
||||
#include <boost/variant/apply_visitor.hpp>
|
||||
#include <boost/variant/get.hpp>
|
||||
#include <boost/variant/static_visitor.hpp>
|
||||
#include <boost/variant/variant.hpp>
|
||||
|
||||
static lg::log_domain log_config("config");
|
||||
#define ERR_CF LOG_STREAM(err, log_config)
|
||||
#define DBG_CF LOG_STREAM(debug, log_config)
|
||||
|
||||
|
||||
// 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::config_attribute_value()
|
||||
: value_()
|
||||
{
|
||||
}
|
||||
|
||||
/** Default implementation, but defined out-of-line for efficiency reasons. */
|
||||
config_attribute_value::~config_attribute_value()
|
||||
{
|
||||
}
|
||||
|
||||
/** Default implementation, but defined out-of-line for efficiency reasons. */
|
||||
config_attribute_value::config_attribute_value(const config_attribute_value &that)
|
||||
: value_(that.value_)
|
||||
{
|
||||
}
|
||||
|
||||
/** Default implementation, but defined out-of-line for efficiency reasons. */
|
||||
config_attribute_value &config_attribute_value::operator=(const config_attribute_value &that)
|
||||
{
|
||||
value_ = that.value_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
config_attribute_value &config_attribute_value::operator=(bool v)
|
||||
{
|
||||
value_ = yes_no(v);
|
||||
return *this;
|
||||
}
|
||||
|
||||
config_attribute_value &config_attribute_value::operator=(int v)
|
||||
{
|
||||
value_ = v;
|
||||
return *this;
|
||||
}
|
||||
|
||||
config_attribute_value &config_attribute_value::operator=(long long 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, getting 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)
|
||||
{
|
||||
// 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 reinterpretations of certain WML strings as
|
||||
* numeric types" (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 == 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;
|
||||
double d = strtod(v.c_str(), &eptr);
|
||||
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;
|
||||
}
|
||||
|
||||
config_attribute_value &config_attribute_value::operator=(const t_string &v)
|
||||
{
|
||||
if (!v.translatable()) return *this = v.str();
|
||||
value_ = v;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
bool config_attribute_value::to_bool(bool def) const
|
||||
{
|
||||
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()(const std::string& 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
|
||||
{
|
||||
return apply_visitor(attribute_numeric_visitor<int>(def));
|
||||
}
|
||||
|
||||
long long config_attribute_value::to_long_long(long long def) const
|
||||
{
|
||||
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
|
||||
{
|
||||
return apply_visitor(attribute_numeric_visitor<size_t>(def));
|
||||
}
|
||||
|
||||
time_t config_attribute_value::to_time_t(time_t def) const
|
||||
{
|
||||
return apply_visitor(attribute_numeric_visitor<time_t>(def));
|
||||
}
|
||||
|
||||
double config_attribute_value::to_double(double def) const
|
||||
{
|
||||
return apply_visitor(attribute_numeric_visitor<double>(def));
|
||||
}
|
||||
|
||||
/// Visitor for converting a variant to a string.
|
||||
class config_attribute_value::string_visitor
|
||||
: public boost::static_visitor<std::string>
|
||||
{
|
||||
const std::string default_;
|
||||
public:
|
||||
string_visitor(const std::string& fallback) : default_(fallback) {}
|
||||
std::string operator()(const boost::blank &) const { return default_; }
|
||||
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 lexical_cast<std::string>(i); }
|
||||
std::string operator()(unsigned long long u) const { return lexical_cast<std::string>(u); }
|
||||
std::string operator()(double d) const { return lexical_cast<std::string>(d); }
|
||||
std::string operator()(const std::string& s) const { return s; }
|
||||
std::string operator()(t_string const &s) const { return s.str(); }
|
||||
};
|
||||
|
||||
std::string config_attribute_value::str(const std::string& fallback) const
|
||||
{
|
||||
return apply_visitor(string_visitor(fallback));
|
||||
}
|
||||
|
||||
t_string config_attribute_value::t_str() const
|
||||
{
|
||||
if (const t_string *p = boost::get<const t_string>(&value_)) return *p;
|
||||
return str();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for an attribute that was never set.
|
||||
*/
|
||||
bool config_attribute_value::blank() const
|
||||
{
|
||||
return boost::get<const boost::blank>(&value_) != nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
if (const std::string *p = boost::get<const std::string>(&value_)) return p->empty();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/// Visitor handling equality checks.
|
||||
class config_attribute_value::equality_visitor
|
||||
: public boost::static_visitor<bool>
|
||||
{
|
||||
public:
|
||||
// Most generic: not equal.
|
||||
template <typename T, typename U>
|
||||
bool operator()(const T &, const U &) const { return false; }
|
||||
|
||||
// Same types are comparable and might be equal.
|
||||
template <typename T>
|
||||
bool operator()(const T & lhs, const T & rhs) const { return lhs == rhs; }
|
||||
|
||||
// Boolean values can be compared.
|
||||
bool operator()(const true_false & lhs, const yes_no & rhs) const { return bool(lhs) == bool(rhs); }
|
||||
bool operator()(const yes_no & lhs, const true_false & rhs) const { return bool(lhs) == bool(rhs); }
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks for equality of the attribute values when viewed as strings.
|
||||
* Exception: Boolean synonyms can be equal ("yes" == "true").
|
||||
* Note: Blanks have no string representation, so do not equal "" (an empty string).
|
||||
*/
|
||||
bool config_attribute_value::operator==(const config_attribute_value &other) const
|
||||
{
|
||||
return boost::apply_visitor(equality_visitor(), value_, other.value_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for equality of the attribute values when viewed as strings.
|
||||
* Exception: Boolean synonyms can be equal ("yes" == "true").
|
||||
* Note: Blanks have no string representation, so do not equal "" (an empty string).
|
||||
* Also note that translatable string are never equal to non translatable strings.
|
||||
*/
|
||||
bool config_attribute_value::equals(const std::string &str) const
|
||||
{
|
||||
config_attribute_value v;
|
||||
v = str;
|
||||
return *this == v;
|
||||
// if c["a"] = "1" then this solution would have resulted in c["a"] == "1" beeing false
|
||||
// because a["a"] is '1' and not '"1"'.
|
||||
// return boost::apply_visitor(std::bind( equality_visitor(), _1, std::cref(str) ), value_);
|
||||
// that's why we don't use it.
|
||||
}
|
||||
|
||||
std::ostream &operator<<(std::ostream &os, const config_attribute_value &v)
|
||||
{
|
||||
// Simple implementation, but defined out-of-line because of the templating
|
||||
// involved.
|
||||
return os << v.value_;
|
||||
}
|
269
src/config_attribute_value.hpp
Normal file
269
src/config_attribute_value.hpp
Normal file
|
@ -0,0 +1,269 @@
|
|||
/*
|
||||
Copyright (C) 2003 - 2017 by David White <dave@whitevine.net>
|
||||
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definitions for the interface to Wesnoth Markup Language (WML).
|
||||
*
|
||||
* This module defines the interface to Wesnoth Markup Language (WML). WML is
|
||||
* a simple hierarchical text-based file format. The format is defined in
|
||||
* Wiki, under BuildingScenariosWML
|
||||
*
|
||||
* All configuration files are stored in this format, and data is sent across
|
||||
* the network in this format. It is thus used extensively throughout the
|
||||
* game.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "global.hpp"
|
||||
|
||||
#include <climits>
|
||||
#include <ctime>
|
||||
#include <iosfwd>
|
||||
#include <iterator>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <type_traits>
|
||||
#include <memory>
|
||||
|
||||
#include <boost/exception/exception.hpp>
|
||||
#include <boost/variant/apply_visitor.hpp>
|
||||
#include <boost/variant/variant.hpp>
|
||||
#include <boost/range/iterator_range.hpp>
|
||||
|
||||
#include "exceptions.hpp"
|
||||
#include "tstring.hpp"
|
||||
|
||||
#ifdef HAVE_CXX14
|
||||
# ifdef __clang__ // Check this first, because clang also defines __GNUC__
|
||||
# ifdef __apple_build_version__ // Apple clang
|
||||
# if (__clang_major__ == 5 && __clang_minor__ >= 1) || __clang_major__ > 5 // Apple clang 5.1+
|
||||
# define USE_HETEROGENOUS_LOOKUPS
|
||||
# endif
|
||||
# else // Non-Apple clang
|
||||
# if (__clang_major__ == 3 && __clang_minor__ >= 4) || __clang_major__ > 3 // clang 3.4+
|
||||
# define USE_HETEROGENOUS_LOOKUPS
|
||||
# endif
|
||||
# endif
|
||||
# elif defined(__GNUC__) && __GNUC__ >= 5 // GCC 5.0+
|
||||
# define USE_HETEROGENOUS_LOOKUPS
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if defined(_MSC_VER) && _MSC_VER >= 1900 // MSVC 2015
|
||||
# define USE_HETEROGENOUS_LOOKUPS
|
||||
#endif
|
||||
|
||||
#ifdef USE_HETEROGENOUS_LOOKUPS
|
||||
#if BOOST_VERSION > 106100
|
||||
#include <boost/utility/string_view.hpp>
|
||||
using config_key_type = boost::string_view;
|
||||
#else
|
||||
#include <boost/utility/string_ref.hpp>
|
||||
using config_key_type = boost::string_ref;
|
||||
#endif
|
||||
#else
|
||||
using config_key_type = const std::string &;
|
||||
#endif
|
||||
|
||||
class enum_tag;
|
||||
|
||||
/**
|
||||
* Variant for storing WML attributes.
|
||||
* The most efficient type is used when assigning a value. For instance,
|
||||
* strings "yes", "no", "true", "false" will be detected and stored as boolean.
|
||||
* @note The blank variant is only used when querying missing attributes.
|
||||
* It is not stored in config objects.
|
||||
*/
|
||||
class config_attribute_value
|
||||
{
|
||||
/// A wrapper for bool to get the correct streaming ("true"/"false").
|
||||
/// Most visitors can simply treat this as bool.
|
||||
public:
|
||||
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) { return os << v.str(); }
|
||||
|
||||
/// 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) { return os << v.str(); }
|
||||
private:
|
||||
/// Visitor for checking equality.
|
||||
class equality_visitor;
|
||||
/// 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:
|
||||
/// Default implementation, but defined out-of-line for efficiency reasons.
|
||||
config_attribute_value();
|
||||
/// Default implementation, but defined out-of-line for efficiency reasons.
|
||||
~config_attribute_value();
|
||||
/// Default implementation, but defined out-of-line for efficiency reasons.
|
||||
config_attribute_value(const config_attribute_value &);
|
||||
/// Default implementation, but defined out-of-line for efficiency reasons.
|
||||
config_attribute_value &operator=(const config_attribute_value &);
|
||||
|
||||
// Numeric assignments:
|
||||
config_attribute_value& operator=(bool v);
|
||||
config_attribute_value& operator=(int v);
|
||||
config_attribute_value& operator=(long v) { return operator=(static_cast<long long>(v)); }
|
||||
config_attribute_value& operator=(long long v);
|
||||
config_attribute_value& operator=(unsigned v) { return operator=(static_cast<unsigned long long>(v)); }
|
||||
config_attribute_value& operator=(unsigned long v) { return operator=(static_cast<unsigned long long>(v)); }
|
||||
config_attribute_value& operator=(unsigned long long v);
|
||||
config_attribute_value& operator=(double v);
|
||||
|
||||
// String assignments:
|
||||
config_attribute_value& operator=(const char *v) { return operator=(std::string(v)); }
|
||||
config_attribute_value& operator=(const std::string &v);
|
||||
config_attribute_value& operator=(const t_string &v);
|
||||
template<typename T>
|
||||
typename std::enable_if<std::is_base_of<enum_tag, T>::value, config_attribute_value &>::type operator=(const T &v)
|
||||
{
|
||||
return operator=(T::enum_to_string(v));
|
||||
}
|
||||
// Extracting as a specific type:
|
||||
bool to_bool(bool def = false) const;
|
||||
int to_int(int 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;
|
||||
std::string str(const std::string& fallback = "") const;
|
||||
t_string t_str() const;
|
||||
/**
|
||||
@param T a type created with MAKE_ENUM macro
|
||||
NOTE: since T::VALUE constants is not of type T but of the underlying enum type you must specify the template parameter explicitly
|
||||
TODO: Fix this in c++11 using constexpr types.
|
||||
*/
|
||||
template<typename T>
|
||||
typename std::enable_if<std::is_base_of<enum_tag, T>::value, T>::type to_enum(const T &v) const
|
||||
{
|
||||
return T::string_to_enum(this->str(), v);
|
||||
}
|
||||
|
||||
// Implicit conversions:
|
||||
operator int() const { return to_int(); }
|
||||
operator std::string() const { return str(); }
|
||||
operator t_string() const { return t_str(); }
|
||||
|
||||
/// Tests for an attribute that was never set.
|
||||
bool blank() const;
|
||||
/// Tests for an attribute that either was never set or was set to "".
|
||||
bool empty() const;
|
||||
|
||||
|
||||
// Comparisons:
|
||||
bool operator==(const config_attribute_value &other) const;
|
||||
bool operator!=(const config_attribute_value &other) const
|
||||
{
|
||||
return !operator==(other);
|
||||
}
|
||||
|
||||
bool equals(const std::string& str) const;
|
||||
// These function prevent t_string creation in case of c["a"] == "b" comparisons.
|
||||
// The templates are needed to prevent using these function in case of c["a"] == 0 comparisons.
|
||||
template<typename T>
|
||||
typename std::enable_if<std::is_same<const std::string, typename std::add_const<T>::type>::value, bool>::type
|
||||
friend operator==(const config_attribute_value &val, const T &str)
|
||||
{
|
||||
return val.equals(str);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
typename std::enable_if<std::is_same<const char*, T>::value, bool>::type
|
||||
friend operator==(const config_attribute_value& val, T str)
|
||||
{
|
||||
return val.equals(std::string(str));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool friend operator==(const T& str, const config_attribute_value& val)
|
||||
{
|
||||
return val == str;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool friend operator!=(const config_attribute_value& val, const T& str)
|
||||
{
|
||||
return !(val == str);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool friend operator!=(const T &str, const config_attribute_value& val)
|
||||
{
|
||||
return !(val == str);
|
||||
}
|
||||
|
||||
// Streaming:
|
||||
friend std::ostream& operator<<(std::ostream& os, const config_attribute_value& v);
|
||||
|
||||
// Visitor support:
|
||||
/// Applies a visitor to the underlying variant.
|
||||
/// (See the documentation for Boost.Variant.)
|
||||
template <typename V>
|
||||
typename V::result_type apply_visitor(const V & visitor) const
|
||||
{
|
||||
return boost::apply_visitor(visitor, value_);
|
||||
}
|
||||
|
||||
private:
|
||||
// Special strings.
|
||||
static const std::string s_yes, s_no;
|
||||
static const std::string s_true, s_false;
|
||||
};
|
Loading…
Add table
Reference in a new issue