The Battle for Wesnoth  1.19.0-dev
variable_info_private.hpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2005 - 2024
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'
30 template<typename TConfig>
31 auto 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 
37 void resolve_negative_value(int size, int& val);
38 void 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  */
56 int parse_index(const char* index_str);
57 int 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  */
86 template<typename R, typename P>
88 {
89 public:
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 
114 template<typename V, typename TResult>
116 
117 template<typename V, typename TResult>
119 
120 /** Adds a '.<key>' to the current variable. */
121 template<typename V>
122 class get_variable_key_visitor : public info_visitor<V, void>
123 {
124 public:
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 
158 private:
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  */
173 template<typename V>
175 {
176 public:
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 
189 private:
190  const int n_;
191 };
192 
193 /** Tries to convert it to an (maybe const) attribute value. */
194 template<typename V>
195 class as_scalar_visitor : public info_visitor_const<V, maybe_const_t<config::attribute_value, V>&>
196 {
197 public:
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  */
210  result_t from_temporary(param_t /*state*/) const
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  */
220 template<>
222  as_scalar_visitor::param_t state) const
223 {
224  return state.temp_val_;
225 }
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  */
231 template<typename V>
232 class as_container_visitor : public info_visitor_const<V, maybe_const_t<config, V>&>
233 {
234 public:
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  */
259 template<typename V>
260 class as_array_visitor : public info_visitor_const<V, maybe_const_t<config::child_itors, V>>
261 {
262 public:
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 
280 template<>
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  */
298 template<typename V, typename THandler, typename... T>
299 class as_range_visitor_base : public info_visitor_const<V, typename THandler::result_t>
300 {
301 public:
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 
321 protected:
322  THandler handler_;
323 };
324 
325 template<typename V>
327 {
328 public:
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 
351 private:
353 };
354 
355 template<typename V>
357 {
358 public:
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 {
393 public:
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 
430 private:
431  std::vector<config>& datasource_;
432 };
433 
435 {
436 public:
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 {
452 public:
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 {
469 public:
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 
494 private:
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:159
config & mandatory_child(config_key_type key, int n=0)
Returns the nth child with the given key, or throws an error if there is none.
Definition: config.cpp:367
void remove_child(config_key_type key, std::size_t index)
Definition: config.cpp:645
std::size_t child_count(config_key_type key) const
Definition: config.cpp:297
boost::iterator_range< child_iterator > child_itors
Definition: config.hpp:282
config & add_child_at(config_key_type key, const config &val, std::size_t index)
Definition: config.cpp:467
void merge_with(const config &c)
Merge config 'c' into this config, overwriting this config's values.
Definition: config.cpp:1126
static bool valid_attribute(config_key_type name)
Definition: config.cpp:150
void swap(config &cfg)
Definition: config.cpp:1340
boost::iterator_range< const_child_iterator > const_child_itors
Definition: config.hpp:283
config & add_child(config_key_type key)
Definition: config.cpp:441
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_container_visitor::result_t result_t
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
typename get_variable_key_visitor::param_t param_t
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)
Implement non-member swap function for std::swap (calls config::swap).
Definition: config.cpp:1347
EXIT_STATUS start(bool clear_id, const std::string &filename, bool take_screenshot, const std::string &screenshot_filename)
Main interface for launching the editor from the title screen.
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.
Definition: game_config.cpp:70
std::size_t index(const std::string &str, const std::size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition: unicode.cpp:70
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:85
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)