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