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