The Battle for Wesnoth  1.19.5+dev
config_attribute_value.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2024
3  by Guillaume Melquiond <guillaume.melquiond@gmail.com>
4  Copyright (C) 2003 by David White <dave@whitevine.net>
5  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
6 
7  This program is free software; you can redistribute it and/or modify
8  it under the terms of the GNU General Public License as published by
9  the Free Software Foundation; either version 2 of the License, or
10  (at your option) any later version.
11  This program is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY.
13 
14  See the COPYING file for more details.
15 */
16 
17 /**
18  * @file
19  * Routines related to configuration-files / WML.
20  */
21 
23 
24 #include "lexical_cast.hpp"
25 #include "log.hpp"
27 
28 #include <cstdlib>
29 
30 static lg::log_domain log_config("config");
31 #define ERR_CF LOG_STREAM(err, log_config)
32 #define DBG_CF LOG_STREAM(debug, log_config)
33 
34 // Special string values.
35 const std::string config_attribute_value::s_yes("yes");
36 const std::string config_attribute_value::s_no("no");
37 const std::string config_attribute_value::s_true("true");
38 const std::string config_attribute_value::s_false("false");
39 
41 {
42  value_ = yes_no(v);
43  return *this;
44 }
45 
47 {
48  value_ = v;
49  return *this;
50 }
51 
53 {
54  if(v > 0) {
55  // We can store this unsigned.
56  return *this = static_cast<unsigned long long>(v);
57  }
58 
59  if(v >= std::numeric_limits<int>::min()) {
60  // We can store this as an int.
61  return *this = static_cast<int>(v);
62  }
63 
64  // Getting to this point should be rare. (Currently, getting here means
65  // something like there was so much draining in a campaign that the
66  // total damage taken is not only negative, but so negative that an
67  // int cannot hold the value.) So rare that it is not worth precise
68  // treatment; just use a double.
69  value_ = static_cast<double>(v);
70  return *this;
71 }
72 
74 {
75  // Use int for smaller numbers.
76  if(v <= std::numeric_limits<int>::max()) {
77  return *this = static_cast<int>(v);
78  }
79 
80  value_ = v;
81  return *this;
82 }
83 
85 {
86  // Try to store integers in other types.
87  if(v > 0.0) {
88  // Convert to unsigned and pass this off to that assignment operator.
89  unsigned long long ull = static_cast<unsigned long long>(v);
90  if(static_cast<double>(ull) == v) {
91  return *this = ull;
92  }
93  } else {
94  // Convert to integer and pass this off to that assignment operator.
95  int i = static_cast<int>(v);
96  if(static_cast<double>(i) == v) {
97  return *this = i;
98  }
99  }
100 
101  // If we get here, this does in fact get stored as a double.
102  value_ = v;
103  return *this;
104 }
105 
106 namespace
107 {
108 /**
109  * Attempts to convert @a source to the template type.
110  * This is to avoid "overzealous reinterpretations of certain WML strings as numeric types".
111  * For example: the version "2.1" and "2.10" are not the same.
112  * Another example: the string "0001" given to [message] should not be displayed to the player as just "1".
113  * @returns true if the conversion was successful and the source string
114  * can be reobtained by streaming the result.
115  */
116 template<typename To>
117 bool from_string_verify(const std::string& source, To& res)
118 {
119  // Check 1: convertible to the target type.
120  std::istringstream in_str(source);
121  if(!(in_str >> res)) {
122  return false;
123  }
124 
125  // Check 2: convertible back to the same string.
126  std::ostringstream out_str;
127  out_str << res;
128  return out_str.str() == source;
129 }
130 } // end anon namespace
131 
133 {
134  // Handle some special strings.
135  if(v.empty()) {
136  value_ = std::move(v);
137  return *this;
138  }
139 
140  if(v == s_yes) {
141  value_ = yes_no(true);
142  return *this;
143  }
144 
145  if(v == s_no) {
146  value_ = yes_no(false);
147  return *this;
148  }
149 
150  if(v == s_true) {
151  value_ = true_false(true);
152  return *this;
153  }
154 
155  if(v == s_false) {
156  value_ = true_false(false);
157  return *this;
158  }
159 
160  // Attempt to convert to a number.
161  char* eptr;
162  double d = strtod(v.c_str(), &eptr);
163  if(*eptr == '\0') {
164  // Possibly a number. See what type it should be stored in.
165  // (All conversions will be from the string since the largest integer
166  // type could have more precision than a double.)
167  if(d > 0.0) {
168  // The largest type for positive integers is unsigned long long.
169  unsigned long long ull = 0;
170  if(from_string_verify<unsigned long long>(v, ull)) {
171  return *this = ull;
172  }
173  } else {
174  // The largest (variant) type for negative integers is int.
175  int i = 0;
176  if(from_string_verify<int>(v, i)) {
177  return *this = i;
178  }
179  }
180 
181  // This does not look like an integer, so it should be a double.
182  // However, make sure it can convert back to the same string (in
183  // case this is a string that just looks like a numeric value).
184  std::ostringstream tester;
185  tester << d;
186  if(tester.str() == v) {
187  value_ = d;
188  return *this;
189  }
190  }
191 
192  // No conversion possible. Store the string.
193  value_ = std::move(v);
194  return *this;
195 }
196 
198 {
199  return operator=(std::string(v));
200 }
201 
203 {
204  // TODO: Currently this acts just like std::string assignment.
205  // Perhaps the underlying variant should take a string_view directly?
206  return operator=(std::string(v));
207 }
208 
210 {
211  if(!v.translatable()) {
212  return *this = v.str();
213  }
214 
215  value_ = v;
216  return *this;
217 }
218 
220 {
221  if(!v.empty()) {
222  *this = v;
223  }
224 }
225 
227 {
228  if(!v.empty()) {
229  *this = v;
230  }
231 }
232 
234 {
235  if(const yes_no* p = utils::get_if<yes_no>(&value_))
236  return *p;
237  if(const true_false* p = utils::get_if<true_false>(&value_))
238  return *p;
239 
240  // No other types are ever recognized as boolean.
241  return def;
242 }
243 
244 namespace
245 {
246 /** Visitor for converting a variant to a numeric type (T). */
247 template<typename T>
248 class attribute_numeric_visitor
249 #ifdef USING_BOOST_VARIANT
250  : public boost::static_visitor<T>
251 #endif
252 {
253 public:
254  // Constructor stores the default value.
255  attribute_numeric_visitor(T def) : def_(def) {}
256 
257  T operator()(const utils::monostate&) const { return def_; }
258  T operator()(bool) const { return def_; }
259  T operator()(int i) const { return static_cast<T>(i); }
260  T operator()(unsigned long long u) const { return static_cast<T>(u); }
261  T operator()(double d) const { return static_cast<T>(d); }
262  T operator()(const std::string& s) const { return lexical_cast_default<T>(s, def_); }
263  T operator()(const t_string&) const { return def_; }
264 
265 private:
266  const T def_;
267 };
268 } // end anon namespace
269 
271 {
272  return apply_visitor(attribute_numeric_visitor<int>(def));
273 }
274 
275 long long config_attribute_value::to_long_long(long long def) const
276 {
277  return apply_visitor(attribute_numeric_visitor<long long>(def));
278 }
279 
280 unsigned config_attribute_value::to_unsigned(unsigned def) const
281 {
282  return apply_visitor(attribute_numeric_visitor<unsigned>(def));
283 }
284 
285 std::size_t config_attribute_value::to_size_t(std::size_t def) const
286 {
287  return apply_visitor(attribute_numeric_visitor<std::size_t>(def));
288 }
289 
290 std::time_t config_attribute_value::to_time_t(std::time_t def) const
291 {
292  return apply_visitor(attribute_numeric_visitor<std::time_t>(def));
293 }
294 
295 double config_attribute_value::to_double(double def) const
296 {
297  return apply_visitor(attribute_numeric_visitor<double>(def));
298 }
299 
300 /** Visitor for converting a variant to a string. */
302 #ifdef USING_BOOST_VARIANT
303  : public boost::static_visitor<std::string>
304 #endif
305 {
306  const std::string default_;
307 
308 public:
309  string_visitor(const std::string& fallback) : default_(fallback) {}
310 
311  std::string operator()(const utils::monostate &) const { return default_; }
312  std::string operator()(const yes_no & b) const { return b.str(); }
313  std::string operator()(const true_false & b) const { return b.str(); }
314  std::string operator()(int i) const { return std::to_string(i); }
315  std::string operator()(unsigned long long u) const { return std::to_string(u); }
316  std::string operator()(double d) const { return lexical_cast<std::string>(d); }
317  std::string operator()(const std::string& s) const { return s; }
318  std::string operator()(const t_string& s) const { return s.str(); }
319 };
320 
321 std::string config_attribute_value::str(const std::string& fallback) const
322 {
323  return apply_visitor(string_visitor(fallback));
324 }
325 
327 {
328  if(const t_string* p = utils::get_if<t_string>(&value_)) {
329  return *p;
330  }
331 
332  return str();
333 }
334 
335 /**
336  * Tests for an attribute that was never set.
337  */
339 {
340  return utils::holds_alternative<utils::monostate>(value_);
341 }
342 
343 /**
344  * Tests for an attribute that either was never set or was set to "".
345  */
347 {
348  if(blank()) {
349  return true;
350  }
351 
352  if(const std::string* p = utils::get_if<std::string>(&value_)) {
353  return p->empty();
354  }
355 
356  return false;
357 }
358 
359 /** Visitor handling equality checks. */
361 #ifdef USING_BOOST_VARIANT
362  : public boost::static_visitor<bool>
363 #endif
364 {
365 public:
366  // Most generic: not equal.
367  template<typename T, typename U>
368  bool operator()(const T&, const U&) const
369  {
370  return false;
371  }
372 
373  // Same types are comparable and might be equal.
374  template<typename T>
375  bool operator()(const T& lhs, const T& rhs) const
376  {
377  return lhs == rhs;
378  }
379 
380  // Boolean values can be compared.
381  bool operator()(const true_false& lhs, const yes_no& rhs) const
382  {
383  return bool(lhs) == bool(rhs);
384  }
385 
386  bool operator()(const yes_no& lhs, const true_false& rhs) const
387  {
388  return bool(lhs) == bool(rhs);
389  }
390 };
391 
392 /**
393  * Checks for equality of the attribute values when viewed as strings.
394  * Exception: Boolean synonyms can be equal ("yes" == "true").
395  * Note: Blanks have no string representation, so do not equal "" (an empty string).
396  */
398 {
399  return utils::visit(equality_visitor(), value_, other.value_);
400 }
401 
402 std::ostream& operator<<(std::ostream& os, const config_attribute_value& v)
403 {
404  // Simple implementation, but defined out-of-line because of the templating
405  // involved.
406  v.apply_visitor([&os](const auto& val) { os << val; });
407  return os;
408 }
409 
410 namespace utils
411 {
412  std::vector<std::string> split(const config_attribute_value& val) {
413  return utils::split(val.str());
414  }
415 }
bool operator()(const yes_no &lhs, const true_false &rhs) const
bool operator()(const T &, const U &) const
bool operator()(const true_false &lhs, const yes_no &rhs) const
bool operator()(const T &lhs, const T &rhs) const
Visitor for converting a variant to a string.
std::string operator()(unsigned long long u) const
std::string operator()(const utils::monostate &) const
std::string operator()(const t_string &s) const
std::string operator()(const true_false &b) const
std::string operator()(const yes_no &b) const
std::string operator()(const std::string &s) const
A wrapper for bool to get the correct streaming ("true"/"false").
A wrapper for bool to get the correct streaming ("yes"/"no").
Variant for storing WML attributes.
static const std::string s_false
value_type value_
The stored value will always use the first type from the variant definition that can represent it and...
std::string str(const std::string &fallback="") const
std::time_t to_time_t(std::time_t def=0) const
static const std::string s_true
auto apply_visitor(const V &visitor) const
Visitor support: Applies a visitor to the underlying variant.
unsigned to_unsigned(unsigned def=0) const
static const std::string s_yes
bool blank() const
Tests for an attribute that was never set.
static const std::string s_no
bool to_bool(bool def=false) const
config_attribute_value & operator=(bool v)
bool operator==(const config_attribute_value &other) const
Checks for equality of the attribute values when viewed as strings.
void write_if_not_empty(const std::string &v)
Calls operator=(const std::string&) if v is not empty.
bool empty() const
Tests for an attribute that either was never set or was set to "".
long long to_long_long(long long def=0) const
std::size_t to_size_t(std::size_t def=0) const
double to_double(double def=0.) const
bool translatable() const
Definition: tstring.hpp:200
bool empty() const
Definition: tstring.hpp:194
const std::string & str() const
Definition: tstring.hpp:198
std::ostream & operator<<(std::ostream &os, const config_attribute_value &v)
static lg::log_domain log_config("config")
Definitions for the interface to Wesnoth Markup Language (WML).
std::size_t i
Definition: function.cpp:1023
New lexcical_cast header.
Standard logging facilities (interface).
std::vector< std::string > split(const config_attribute_value &val)
mock_party p
static map_location::direction s
#define d
#define b