forked from wesnoth/wesnoth
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathconfig_attribute_value.cpp
439 lines (376 loc) · 11.4 KB
/
config_attribute_value.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
/*
Copyright (C) 2003 by David White <[email protected]>
Copyright (C) 2005 - 2018 by Guillaume Melquiond <[email protected]>
Part of the Battle for Wesnoth Project https://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 "serialization/string_utils.hpp"
#include <cstdlib>
#include <cstring>
#include <deque>
#include <functional>
#include <istream>
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: convertible to the target type.
std::istringstream in_str(source);
if(!(in_str >> res)) {
return false;
}
// Check 2: convertible back to the same string.
std::ostringstream out_str;
out_str << res;
return out_str.str() == source;
}
} // end anon namespace
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;
}
void config_attribute_value::write_if_not_empty(const std::string& v)
{
if(!v.empty()) {
*this = v;
}
}
bool config_attribute_value::to_bool(bool def) const
{
if(const yes_no* p = utils::get_if<yes_no>(&value_))
return *p;
if(const true_false* p = utils::get_if<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
#ifdef USING_BOOST_VARIANT
: public boost::static_visitor<T>
#endif
{
public:
// Constructor stores the default value.
attribute_numeric_visitor(T def) : def_(def) {}
T operator()(const utils::monostate&) 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()(const t_string&) const { return def_; }
private:
const T def_;
};
} // end anon namespace
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));
}
std::size_t config_attribute_value::to_size_t(std::size_t def) const
{
return apply_visitor(attribute_numeric_visitor<std::size_t>(def));
}
std::time_t config_attribute_value::to_time_t(std::time_t def) const
{
return apply_visitor(attribute_numeric_visitor<std::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
#ifdef USING_BOOST_VARIANT
: public boost::static_visitor<std::string>
#endif
{
const std::string default_;
public:
string_visitor(const std::string& fallback) : default_(fallback) {}
std::string operator()(const utils::monostate &) 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 std::to_string(i); }
std::string operator()(unsigned long long u) const { return std::to_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()(const t_string& 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 = utils::get_if<t_string>(&value_)) {
return *p;
}
return str();
}
/**
* Tests for an attribute that was never set.
*/
bool config_attribute_value::blank() const
{
return utils::holds_alternative<utils::monostate>(value_);
}
/**
* Tests for an attribute that either was never set or was set to "".
*/
bool config_attribute_value::empty() const
{
if(blank()) {
return true;
}
if(const std::string* p = utils::get_if<std::string>(&value_)) {
return p->empty();
}
return false;
}
/** Visitor handling equality checks. */
class config_attribute_value::equality_visitor
#ifdef USING_BOOST_VARIANT
: public boost::static_visitor<bool>
#endif
{
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 utils::visit(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" being false
// because a["a"] is '1' and not '"1"'.
// return boost::apply_visitor(std::bind( equality_visitor(), std::placeholders::_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.
v.apply_visitor([&os](const auto& val) { os << val; });
return os;
}
namespace utils
{
std::vector<std::string> split(const config_attribute_value& val) {
return utils::split(val.str());
}
}