The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
variable_info_private.hpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2005 - 2025
3 by Philippe Plantier <ayin@anathas.org>
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#pragma once
18
19#include "game_config.hpp"
20
21#include <stdexcept>
22
24{
25// ==================================================================
26// General helper functions
27// ==================================================================
28
29// TConfig is either 'config' or 'const config'
30template<typename TConfig>
31auto get_child_range(TConfig& cfg, const std::string& key, int start, int count) -> decltype(cfg.child_range(key))
32{
33 auto res = cfg.child_range(key);
34 return {res.begin() + start, res.begin() + start + count};
35}
36
37void resolve_negative_value(int size, int& val);
38void resolve_negative_value(int size, int& val)
39{
40 if(val < 0) {
41 val = size + val;
42 }
43
44 // val is still < 0? We don't accept!
45 if(val < 0) {
47 }
48}
49
51
52/**
53 * Parses a ']' terminated string.
54 * This is a important optimization of lexical_cast_default
55 */
56int parse_index(const char* index_str);
57int parse_index(const char* index_str)
58{
59 char* endptr;
60 int res = strtol(index_str, &endptr, 10);
61
62 if(*endptr != ']' || res > static_cast<int>(game_config::max_loop) || endptr == index_str) {
64 }
65
66 return res;
67}
68
69// ==================================================================
70// Visitor interface
71// ==================================================================
72
73/**
74 * Visitor base class.
75 *
76 * This provides the interface for the main functions each visitor can implement. The default implementation of
77 * each function simply throws @ref invalid_variablename_exception.
78 *
79 * This class also provides two type aliases corresponding to the function return value and argument types.
80 * Note that visitors shouldn't inherit from this directly and instead use the @ref info_visitor and
81 * @ref info_visitor_const wrappers, since both fully specify the parameter type.
82 *
83 * @tparam R Return value type.
84 * @tparam P Argument type.
85 */
86template<typename R, typename P>
88{
89public:
90 using result_t = R;
91 using param_t = P&;
92
93#define DEFAULTHANDLER(name) \
94 result_t name(param_t) const \
95 { \
96 throw invalid_variablename_exception(); \
97 }
98
99 /** For use if the variable name was previously empty. This can only happen during calculate_value. */
100 DEFAULTHANDLER(from_start)
101
102 /** For use if the variable ended with a .somename. */
103 DEFAULTHANDLER(from_named)
104
105 /** For use if the variable ended with .somename[someindex]. */
106 DEFAULTHANDLER(from_indexed)
107
108 /** For use if the variable is a readonly value (.somename.length). */
109 DEFAULTHANDLER(from_temporary)
110
111#undef DEFAULTHANDLER
112};
113
114template<typename V, typename TResult>
116
117template<typename V, typename TResult>
119
120/** Adds a '.<key>' to the current variable. */
121template<typename V>
123{
124public:
125 // Import typedefs from base class.
127
128 get_variable_key_visitor(const std::string& key)
129 : key_(key)
130 {
133 }
134 }
135
136 void from_named(param_t state) const
137 {
138 if(key_ == "length") {
139 state.temp_val_ = state.child_->child_count(state.key_);
140 state.type_ = state_temporary;
141 return;
142 }
143
144 return do_from_config(V::get_child_at(*state.child_, state.key_, 0), state);
145 }
146
147 void from_start(param_t state) const
148 {
149 return do_from_config(*state.child_, state);
150 }
151
152 void from_indexed(param_t state) const
153 {
154 // We do not support aaa[0].length
155 return do_from_config(V::get_child_at(*state.child_, state.key_, state.index_), state);
156 }
157
158private:
160 {
161 state.type_ = state_named;
162 state.key_ = key_;
163 state.child_ = &cfg;
164 }
165
166 const std::string& key_;
167};
168
169/**
170 * Appends an [index] to the variable.
171 * We only support from_named since [index][index2] or a.length[index] both don't make sense.
172 */
173template<typename V>
175{
176public:
178 : n_(n)
179 {
180 }
181
183 {
184 state.index_ = n_;
185 resolve_negative_value(state.child_->child_count(state.key_), state.index_);
186 state.type_ = state_indexed;
187 }
188
189private:
190 const int n_;
191};
192
193/** Tries to convert it to an (maybe const) attribute value. */
194template<typename V>
195class as_scalar_visitor : public info_visitor_const<V, maybe_const_t<config::attribute_value, V>&>
196{
197public:
198 // Import typedefs from base class.
201
203 {
204 return (*state.child_)[state.key_];
205 }
206
207 /**
208 * Only implemented for read-only variable_info. Other types use the default throw implementation.
209 */
211 {
213 }
214};
215
216/**
217 * Values like '.length' are readonly so we only support reading them, especially since we don't
218 * want to return non-const references.
219 */
220template<>
226
227/**
228 * Tries to convert to a [const] config&. Unlike range based operation this also supports 'from_start'.
229 * NOTE: Currently getting the 'from_start' case here is impossible, because we always apply at least one string key.
230 */
231template<typename V>
232class as_container_visitor : public info_visitor_const<V, maybe_const_t<config, V>&>
233{
234public:
235 // Import typedefs from base class.
238
240 {
241 return V::get_child_at(*state.child_, state.key_, 0);
242 }
243
245 {
246 return *state.child_;
247 }
248
250 {
251 return V::get_child_at(*state.child_, state.key_, state.index_);
252 }
253};
254
255/**
256 * This currently isn't implemented as a range-based operation because doing it on something like range
257 * 2-5 on vi_policy_const if child_ has only 4 elements would be too hard to implement.
258 */
259template<typename V>
260class as_array_visitor : public info_visitor_const<V, maybe_const_t<config::child_itors, V>>
261{
262public:
263 // Import typedefs from base class.
266
268 {
269 return get_child_range(*state.child_, state.key_, 0, state.child_->child_count(state.key_));
270 }
271
273 {
274 // Ensure we have a config at the given explicit position.
275 V::get_child_at(*state.child_, state.key_, state.index_);
276 return get_child_range(*state.child_, state.key_, state.index_, 1);
277 }
278};
279
280template<>
282{
283 if(static_cast<int>(state.child_->child_count(state.key_)) <= state.index_) {
284 return get_child_range(non_empty_const_cfg, "_", 0, 1);
285 }
286
287 return get_child_range(*state.child_, state.key_, state.index_, 1);
288}
289
290/**
291 * @tparam THandler Handler type. Should implement an operator() with the signature:
292 * '(config&, const std::string&, int, int) -> THandler::result_t'
293 *
294 * That does the actual work on the range of children of cfg with name 'name'.
295 * Note this is currently only used by the insert/append/replace/merge operations, so V is always
296 * vi_policy_create.
297 */
298template<typename V, typename THandler, typename... T>
299class as_range_visitor_base : public info_visitor_const<V, typename THandler::result_t>
300{
301public:
302 // Import typedefs from base class.
305
307 : handler_(std::forward<T>(args)...)
308 {
309 }
310
312 {
313 return handler_(*state.child_, state.key_, 0, state.child_->child_count(state.key_));
314 }
315
317 {
318 return handler_(*state.child_, state.key_, state.index_, state.index_ + 1);
319 }
320
321protected:
322 THandler handler_;
323};
324
325template<typename V>
327{
328public:
329 // Import typedefs from base class.
331
332 clear_value_visitor(bool only_tables)
333 : only_tables_(only_tables)
334 {
335 }
336
337 void from_named(param_t state) const
338 {
339 if(!only_tables_) {
340 state.child_->remove_attribute(state.key_);
341 }
342
343 state.child_->clear_children(state.key_);
344 }
345
346 void from_indexed(param_t state) const
347 {
348 state.child_->remove_child(state.key_, state.index_);
349 }
350
351private:
353};
354
355template<typename V>
357{
358public:
359 // Import typedefs from base class.
361
362 bool from_named(param_t state) const
363 {
364 return state.child_->has_child(state.key_);
365 }
366
367 bool from_indexed(param_t state) const
368 {
369 return state.child_->child_count(state.key_) > static_cast<std::size_t>(state.index_);
370 }
371
372 bool from_start(param_t) const
373 {
374 return true;
375 }
376
378 {
379 return false;
380 }
381};
382
383// ==================================================================
384// Range manipulation interface
385// ==================================================================
386
387/**
388 * Replaces the child in [startindex, endindex) with 'source'
389 * 'insert' and 'append' are subcases of this.
390 */
392{
393public:
395 replace_range_h(std::vector<config>& source)
396 : datasource_(source)
397 {
398 }
399
400 result_t operator()(config& child, const std::string& key, int startindex, int endindex) const
401 {
402 assert(endindex - startindex >= 0);
403 if(endindex > 0) {
404 // NOTE: currently this is only called from as_range_visitor_base<vi_policy_create>
405 // Based on that assumption we use vi_policy_create::get_child_at here instead of making this
406 // a class template.
407 vi_policy_create::get_child_at(child, key, endindex - 1);
408 }
409
410 int size_diff = datasource_.size() - (endindex - startindex);
411
412 // remove configs first
413 while(size_diff < 0) {
414 child.remove_child(key, startindex);
415 ++size_diff;
416 }
417
418 std::size_t index = 0;
419 for(index = 0; index < static_cast<std::size_t>(size_diff); ++index) {
420 child.add_child_at(key, config(), startindex + index).swap(datasource_[index]);
421 }
422
423 for(; index < datasource_.size(); ++index) {
424 child.mandatory_child(key, startindex + index).swap(datasource_[index]);
425 }
426
427 return get_child_range(child, key, startindex, datasource_.size());
428 }
429
430private:
431 std::vector<config>& datasource_;
432};
433
435{
436public:
438 insert_range_h(std::vector<config>& source)
439 : replace_range_h(source)
440 {
441 }
442
443 result_t operator()(config& child, const std::string& key, int startindex, int /*endindex*/) const
444 {
445 // insert == replace empty range with data.
446 return replace_range_h::operator()(child, key, startindex, startindex);
447 }
448};
449
451{
452public:
454 append_range_h(std::vector<config>& source)
455 : insert_range_h(source)
456 {
457 }
458
459 result_t operator()(config& child, const std::string& key, int /*startindex*/, int /*endindex*/) const
460 {
461 // append == insert at end.
462 int insert_pos = child.child_count(key);
463 return insert_range_h::operator()(child, key, insert_pos, insert_pos /*ignored by insert_range_h*/);
464 }
465};
466
468{
469public:
470 typedef void result_t;
471 merge_range_h(std::vector<config>& source)
472 : datasource_(source)
473 {
474 }
475
476 void operator()(config& child, const std::string& key, int startindex, int /*endindex*/) const
477 {
478 // The merge_with function only accepts configs so we convert vector -> config.
479 config datatemp;
480
481 // Add empty config to 'shift' the merge to startindex
482 for(int index = 0; index < startindex; ++index) {
483 datatemp.add_child(key);
484 }
485
486 // move datasource_ -> datatemp
487 for(std::size_t index = 0; index < datasource_.size(); ++index) {
488 datatemp.add_child(key).swap(datasource_[index]);
489 }
490
491 child.merge_with(datatemp);
492 }
493
494private:
495 std::vector<config>& datasource_;
496};
497} // end namespace variable_info_implementation
Variant for storing WML attributes.
A config object defines a single node in a WML file, with access to child nodes.
Definition config.hpp:157
config & add_child(std::string_view key)
Definition config.cpp:436
boost::iterator_range< child_iterator > child_itors
Definition config.hpp:280
child_itors child_range(std::string_view key)
Definition config.cpp:268
static bool valid_attribute(std::string_view name)
Definition config.cpp:152
void merge_with(const config &c)
Merge config 'c' into this config, overwriting this config's values.
Definition config.cpp:1097
void swap(config &cfg) noexcept
Definition config.cpp:1280
void remove_child(std::string_view key, std::size_t index)
Definition config.cpp:623
std::size_t child_count(std::string_view key) const
Definition config.cpp:292
config & add_child_at(std::string_view key, const config &val, std::size_t index)
Definition config.cpp:465
boost::iterator_range< const_child_iterator > const_child_itors
Definition config.hpp:281
config & mandatory_child(std::string_view key, int n=0)
Returns the nth child with the given key, or throws an error if there is none.
Definition config.cpp:362
result_t operator()(config &child, const std::string &key, int, int) const
This currently isn't implemented as a range-based operation because doing it on something like range ...
typename as_range_visitor_base::result_t result_t
Tries to convert it to an (maybe const) attribute value.
result_t from_temporary(param_t) const
Only implemented for read-only variable_info.
typename exists_as_container_visitor::param_t param_t
void from_named(typename get_variable_index_visitor::param_t state) const
void do_from_config(maybe_const_t< config, V > &cfg, param_t state) const
result_t operator()(config &child, const std::string &key, int startindex, int) const
void operator()(config &child, const std::string &key, int startindex, int) const
Replaces the child in [startindex, endindex) with 'source' 'insert' and 'append' are subcases of this...
result_t operator()(config &child, const std::string &key, int startindex, int endindex) const
static config & get_child_at(config &cfg, const std::string &key, int index)
void swap(config &lhs, config &rhs) noexcept
Implement non-member swap function for std::swap (calls config::swap).
Definition config.cpp:1287
const config * cfg
const std::size_t max_loop
The maximum number of hexes on a map and items in an array and also used as maximum in wml loops.
STL namespace.
const config non_empty_const_cfg("_")
int parse_index(const char *index_str)
Parses a ']' terminated string.
@ state_named
The result of .someval.
@ state_indexed
The result of .someval[index].
@ state_temporary
The result of .length.
void resolve_negative_value(int size, int &val)
auto get_child_range(TConfig &cfg, const std::string &key, int start, int count) -> decltype(cfg.child_range(key))
static map_location::direction n
typename variable_info_implementation::maybe_const< T, V >::type maybe_const_t
Helper template alias for maybe_const, defined at global scope for convenience.
#define DEFAULTHANDLER(name)