The Battle for Wesnoth  1.17.23+dev
config_attribute_value.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2023
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 #include <cstring>
30 #include <deque>
31 #include <functional>
32 #include <istream>
33 
34 static lg::log_domain log_config("config");
35 #define ERR_CF LOG_STREAM(err, log_config)
36 #define DBG_CF LOG_STREAM(debug, log_config)
37 
38 // Special string values.
39 const std::string config_attribute_value::s_yes("yes");
40 const std::string config_attribute_value::s_no("no");
41 const std::string config_attribute_value::s_true("true");
42 const std::string config_attribute_value::s_false("false");
43 
44 /** Default implementation, but defined out-of-line for efficiency reasons. */
46  : value_()
47 {
48 }
49 
50 /** Default implementation, but defined out-of-line for efficiency reasons. */
52 {
53 }
54 
55 /** Default implementation, but defined out-of-line for efficiency reasons. */
57  : value_(that.value_)
58 {
59 }
60 
61 /** Default implementation, but defined out-of-line for efficiency reasons. */
63 {
64  value_ = that.value_;
65  return *this;
66 }
67 
69 {
70  value_ = yes_no(v);
71  return *this;
72 }
73 
75 {
76  value_ = v;
77  return *this;
78 }
79 
81 {
82  if(v > 0) {
83  // We can store this unsigned.
84  return *this = static_cast<unsigned long long>(v);
85  }
86 
87  if(v >= INT_MIN) {
88  // We can store this as an int.
89  return *this = static_cast<int>(v);
90  }
91 
92  // Getting to this point should be rare. (Currently, getting here means
93  // something like there was so much draining in a campaign that the
94  // total damage taken is not only negative, but so negative that an
95  // int cannot hold the value.) So rare that it is not worth precise
96  // treatment; just use a double.
97  value_ = static_cast<double>(v);
98  return *this;
99 }
100 
102 {
103  // Use int for smaller numbers.
104  if(v <= INT_MAX) {
105  return *this = static_cast<int>(v);
106  }
107 
108  value_ = v;
109  return *this;
110 }
111 
113 {
114  // Try to store integers in other types.
115  if(v > 0.0) {
116  // Convert to unsigned and pass this off to that assignment operator.
117  unsigned long long ull = static_cast<unsigned long long>(v);
118  if(static_cast<double>(ull) == v) {
119  return *this = ull;
120  }
121  } else {
122  // Convert to integer and pass this off to that assignment operator.
123  int i = static_cast<int>(v);
124  if(static_cast<double>(i) == v) {
125  return *this = i;
126  }
127  }
128 
129  // If we get here, this does in fact get stored as a double.
130  value_ = v;
131  return *this;
132 }
133 
134 namespace
135 {
136 /**
137  * Attempts to convert @a source to the template type.
138  * This is to avoid "overzealous reinterpretations of certain WML strings as numeric types".
139  * For example: the version "2.1" and "2.10" are not the same.
140  * Another example: the string "0001" given to [message] should not be displayed to the player as just "1".
141  * @returns true if the conversion was successful and the source string
142  * can be reobtained by streaming the result.
143  */
144 template<typename To>
145 bool from_string_verify(const std::string& source, To& res)
146 {
147  // Check 1: convertible to the target type.
148  std::istringstream in_str(source);
149  if(!(in_str >> res)) {
150  return false;
151  }
152 
153  // Check 2: convertible back to the same string.
154  std::ostringstream out_str;
155  out_str << res;
156  return out_str.str() == source;
157 }
158 } // end anon namespace
159 
161 {
162  // Handle some special strings.
163  if(v.empty()) {
164  value_ = v;
165  return *this;
166  }
167 
168  if(v == s_yes) {
169  value_ = yes_no(true);
170  return *this;
171  }
172 
173  if(v == s_no) {
174  value_ = yes_no(false);
175  return *this;
176  }
177 
178  if(v == s_true) {
179  value_ = true_false(true);
180  return *this;
181  }
182 
183  if(v == s_false) {
184  value_ = true_false(false);
185  return *this;
186  }
187 
188  // Attempt to convert to a number.
189  char* eptr;
190  double d = strtod(v.c_str(), &eptr);
191  if(*eptr == '\0') {
192  // Possibly a number. See what type it should be stored in.
193  // (All conversions will be from the string since the largest integer
194  // type could have more precision than a double.)
195  if(d > 0.0) {
196  // The largest type for positive integers is unsigned long long.
197  unsigned long long ull = 0;
198  if(from_string_verify<unsigned long long>(v, ull)) {
199  return *this = ull;
200  }
201  } else {
202  // The largest (variant) type for negative integers is int.
203  int i = 0;
204  if(from_string_verify<int>(v, i)) {
205  return *this = i;
206  }
207  }
208 
209  // This does not look like an integer, so it should be a double.
210  // However, make sure it can convert back to the same string (in
211  // case this is a string that just looks like a numeric value).
212  std::ostringstream tester;
213  tester << d;
214  if(tester.str() == v) {
215  value_ = d;
216  return *this;
217  }
218  }
219 
220  // No conversion possible. Store the string.
221  value_ = v;
222  return *this;
223 }
224 
226 {
227  // TODO: Currently this acts just like std::string assignment.
228  // Perhaps the underlying variant should take a string_view directly?
229  return operator=(std::string(v));
230 
231 }
233 {
234  if(!v.translatable()) {
235  return *this = v.str();
236  }
237 
238  value_ = v;
239  return *this;
240 }
241 
243 {
244  if(!v.empty()) {
245  *this = v;
246  }
247 }
248 
250 {
251  if(const yes_no* p = utils::get_if<yes_no>(&value_))
252  return *p;
253  if(const true_false* p = utils::get_if<true_false>(&value_))
254  return *p;
255 
256  // No other types are ever recognized as boolean.
257  return def;
258 }
259 
260 namespace
261 {
262 /** Visitor for converting a variant to a numeric type (T). */
263 template<typename T>
264 class attribute_numeric_visitor
265 #ifdef USING_BOOST_VARIANT
266  : public boost::static_visitor<T>
267 #endif
268 {
269 public:
270  // Constructor stores the default value.
271  attribute_numeric_visitor(T def) : def_(def) {}
272 
273  T operator()(const utils::monostate&) const { return def_; }
274  T operator()(bool) const { return def_; }
275  T operator()(int i) const { return static_cast<T>(i); }
276  T operator()(unsigned long long u) const { return static_cast<T>(u); }
277  T operator()(double d) const { return static_cast<T>(d); }
278  T operator()(const std::string& s) const { return lexical_cast_default<T>(s, def_); }
279  T operator()(const t_string&) const { return def_; }
280 
281 private:
282  const T def_;
283 };
284 } // end anon namespace
285 
287 {
288  return apply_visitor(attribute_numeric_visitor<int>(def));
289 }
290 
291 long long config_attribute_value::to_long_long(long long def) const
292 {
293  return apply_visitor(attribute_numeric_visitor<long long>(def));
294 }
295 
296 unsigned config_attribute_value::to_unsigned(unsigned def) const
297 {
298  return apply_visitor(attribute_numeric_visitor<unsigned>(def));
299 }
300 
301 std::size_t config_attribute_value::to_size_t(std::size_t def) const
302 {
303  return apply_visitor(attribute_numeric_visitor<std::size_t>(def));
304 }
305 
306 std::time_t config_attribute_value::to_time_t(std::time_t def) const
307 {
308  return apply_visitor(attribute_numeric_visitor<std::time_t>(def));
309 }
310 
311 double config_attribute_value::to_double(double def) const
312 {
313  return apply_visitor(attribute_numeric_visitor<double>(def));
314 }
315 
316 /** Visitor for converting a variant to a string. */
318 #ifdef USING_BOOST_VARIANT
319  : public boost::static_visitor<std::string>
320 #endif
321 {
322  const std::string default_;
323 
324 public:
325  string_visitor(const std::string& fallback) : default_(fallback) {}
326 
327  std::string operator()(const utils::monostate &) const { return default_; }
328  std::string operator()(const yes_no & b) const { return b.str(); }
329  std::string operator()(const true_false & b) const { return b.str(); }
330  std::string operator()(int i) const { return std::to_string(i); }
331  std::string operator()(unsigned long long u) const { return std::to_string(u); }
332  std::string operator()(double d) const { return lexical_cast<std::string>(d); }
333  std::string operator()(const std::string& s) const { return s; }
334  std::string operator()(const t_string& s) const { return s.str(); }
335 };
336 
337 std::string config_attribute_value::str(const std::string& fallback) const
338 {
339  return apply_visitor(string_visitor(fallback));
340 }
341 
343 {
344  if(const t_string* p = utils::get_if<t_string>(&value_)) {
345  return *p;
346  }
347 
348  return str();
349 }
350 
351 /**
352  * Tests for an attribute that was never set.
353  */
355 {
356  return utils::holds_alternative<utils::monostate>(value_);
357 }
358 
359 /**
360  * Tests for an attribute that either was never set or was set to "".
361  */
363 {
364  if(blank()) {
365  return true;
366  }
367 
368  if(const std::string* p = utils::get_if<std::string>(&value_)) {
369  return p->empty();
370  }
371 
372  return false;
373 }
374 
375 /** Visitor handling equality checks. */
377 #ifdef USING_BOOST_VARIANT
378  : public boost::static_visitor<bool>
379 #endif
380 {
381 public:
382  // Most generic: not equal.
383  template<typename T, typename U>
384  bool operator()(const T&, const U&) const
385  {
386  return false;
387  }
388 
389  // Same types are comparable and might be equal.
390  template<typename T>
391  bool operator()(const T& lhs, const T& rhs) const
392  {
393  return lhs == rhs;
394  }
395 
396  // Boolean values can be compared.
397  bool operator()(const true_false& lhs, const yes_no& rhs) const
398  {
399  return bool(lhs) == bool(rhs);
400  }
401 
402  bool operator()(const yes_no& lhs, const true_false& rhs) const
403  {
404  return bool(lhs) == bool(rhs);
405  }
406 };
407 
408 /**
409  * Checks for equality of the attribute values when viewed as strings.
410  * Exception: Boolean synonyms can be equal ("yes" == "true").
411  * Note: Blanks have no string representation, so do not equal "" (an empty string).
412  */
414 {
415  return utils::visit(equality_visitor(), value_, other.value_);
416 }
417 
418 /**
419  * Checks for equality of the attribute values when viewed as strings.
420  * Exception: Boolean synonyms can be equal ("yes" == "true").
421  * Note: Blanks have no string representation, so do not equal "" (an empty string).
422  * Also note that translatable string are never equal to non translatable strings.
423  */
424 bool config_attribute_value::equals(const std::string& str) const
425 {
427  v = str;
428  return *this == v;
429  // if c["a"] = "1" then this solution would have resulted in c["a"] == "1" being false
430  // because a["a"] is '1' and not '"1"'.
431  // return boost::apply_visitor(std::bind( equality_visitor(), std::placeholders::_1, std::cref(str) ), value_);
432  // that's why we don't use it.
433 }
434 
435 std::ostream& operator<<(std::ostream& os, const config_attribute_value& v)
436 {
437  // Simple implementation, but defined out-of-line because of the templating
438  // involved.
439  v.apply_visitor([&os](const auto& val) { os << val; });
440  return os;
441 }
442 
443 namespace utils
444 {
445  std::vector<std::string> split(const config_attribute_value& val) {
446  return utils::split(val.str());
447  }
448 }
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.
config_attribute_value & operator=(const config_attribute_value &)
Default implementation, but defined out-of-line for efficiency reasons.
bool equals(const std::string &str) const
Checks for equality of the attribute values when viewed as strings.
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
config_attribute_value()
Default implementation, but defined out-of-line for efficiency reasons.
bool to_bool(bool def=false) const
~config_attribute_value()
Default implementation, but defined out-of-line for efficiency reasons.
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:193
const std::string & str() const
Definition: tstring.hpp:191
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:968
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