The Battle for Wesnoth  1.13.4+dev
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
variable_info.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 by David White <[email protected]>
3  Copyright (C) 2005 - 2016 by Philippe Plantier <[email protected]>
4 
5  Part of the Battle for Wesnoth Project http://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  * Manage WML-variables.
20  */
21 #include "variable_info.hpp"
22 #include "game_config.hpp"
23 #include "config_assign.hpp"
24 
25 #include <stdexcept>
26 
27 using namespace variable_info_detail;
28 
29 /// general helpers
30 namespace
31 {
32  /// TConfig is eigher 'config' or 'const config'
33  template<typename TConfig>
34  auto get_child_range(TConfig& cfg, const std::string& key, int start, int count) -> decltype(cfg.child_range(key))
35  {
36  auto res = cfg.child_range(key);
37  std::advance(res.first, start);
38  res.second = res.first;
39  std::advance(res.second, count);
40  return res;
41  }
42 
43  void resolve_negative_value(int size, int& val)
44  {
45  if(val < 0)
46  {
47  val = size + val;
48  }
49  //val is still < 0? We don't accept!
50  if(val < 0)
51  {
53  }
54  }
55 
56  template<const variable_info_type vit>
57  typename maybe_const<vit, config>::type& get_child_at(typename maybe_const<vit, config>::type& cfg, const std::string& key, int index = 0);
58 
59  template<>
60  config& get_child_at<vit_create_if_not_existent>(config& cfg, const std::string& key, int index)
61  {
62  assert(index >= 0);
63  // the 'create_if_not_existent' logic.
64  while(static_cast<int>(cfg.child_count(key)) <= index)
65  {
66  cfg.add_child(key);
67  }
68  return cfg.child(key, index);
69  }
70 
71  //helper variable for get_child_at<vit_const>
72  const config empty_const_cfg;
73  const config non_empty_const_cfg = config_of("_", config());
74 
75  template<>
76  const config& get_child_at<vit_const>(const config& cfg, const std::string& key, int index)
77  {
78  assert(index >= 0);
79  //cfg.child_or_empty does not support index parameter
80  if(const config& child = cfg.child(key, index))
81  {
82  return child;
83  }
84  else
85  {
86  return empty_const_cfg;
87  }
88  }
89 
90  template<>
91  config& get_child_at<vit_throw_if_not_existent>(config& cfg, const std::string& key, int index)
92  {
93  assert(index >= 0);
94  if(config& child = cfg.child(key, index))
95  {
96  return child;
97  }
98  else
99  {
101  }
102  }
103 
104  template <typename TVal>
105  config::attribute_value attribute_value_of(const TVal& val)
106  {
108  v = val;
109  return v;
110  }
111 
112  /// @param visitor
113  /// TVisitor should have 4 methods:
114  /// from_named if the variable ended with a .somename
115  /// from_indexed if the variable enden with .somename[someindex]
116  /// from_temporary if the variable is a readonly value (.somename.length)
117  /// from_start if the variablename was previously empty, this can only happen
118  /// during calculate_value()
119  /// TVisitor should derive from variable_info_visitor(_const) which makes default implementation for these (as not supported)
120  template <typename TVisitor>
121  typename TVisitor::result_type apply_visitor(const TVisitor& visitor, typename TVisitor::param_type state)
122  {
123  switch(state.type_)
124  {
125  case state_start:
126  return visitor.from_start(state);
127  case state_named:
128  return visitor.from_named(state);
129  case state_indexed:
130  return visitor.from_indexed(state);
131  case state_temporary:
132  return visitor.from_temporary(state);
133  }
134  throw std::range_error("Failed to convert the TVisitor::param_type type");
135  }
136 
137  template <const variable_info_type vit, typename TResult>
138  class variable_info_visitor
139  {
140  public:
141  typedef TResult result_type;
142  typedef variable_info_state<vit>& param_type;
143 #define DEFAULTHANDLER(name) result_type name(param_type) const { throw invalid_variablename_exception(); }
144  DEFAULTHANDLER(from_start)
145  DEFAULTHANDLER(from_named)
146  DEFAULTHANDLER(from_indexed)
147  DEFAULTHANDLER(from_temporary)
148 #undef DEFAULTHANDLER
149  };
150 
151  template <const variable_info_type vit, typename TResult>
152  class variable_info_visitor_const
153  {
154  public:
155  typedef TResult result_type;
156  typedef const variable_info_state<vit>& param_type;
157 #define DEFAULTHANDLER(name) result_type name(param_type) const { throw invalid_variablename_exception(); }
158  DEFAULTHANDLER(from_start)
159  DEFAULTHANDLER(from_named)
160  DEFAULTHANDLER(from_indexed)
161  DEFAULTHANDLER(from_temporary)
162 #undef DEFAULTHANDLER
163  };
164 }
165 /// calculate_value() helpers
166 namespace
167 {
168  /// Parses a ']' terminated string.
169  /// This is a important optimisation of lexical_cast_default
170  int parse_index(const char* index_str)
171  {
172  char* endptr;
173  int res = strtol(index_str, &endptr, 10);
174 
175  if (*endptr != ']' || res > int(game_config::max_loop) || endptr == index_str)
176  {
178  }
179  return res;
180  }
181 
182  /// Adds a '.<key>' to the current variable
183  template<const variable_info_type vit>
184  class get_variable_key_visitor
185  : public variable_info_visitor<vit, void>
186  {
187  public:
188  get_variable_key_visitor(const std::string& key) : key_(key) {
189  if (!config::valid_id(key_)) {
191  }
192  }
193  void from_named(typename get_variable_key_visitor::param_type state) const
194  {
195  if(key_ == "length")
196  {
197  state.temp_val_ = state.child_->child_count(state.key_);
198  state.type_ = state_temporary;
199  return;
200  }
201  else
202  {
203  return do_from_config(get_child_at<vit>(*state.child_, state.key_, 0), state);
204  }
205  }
206  void from_start(typename get_variable_key_visitor::param_type state) const
207  {
208  return do_from_config(*state.child_, state);
209  }
210  void from_indexed(typename get_variable_key_visitor::param_type state) const
211  {
212  //we do not support aaa[0].length
213  return do_from_config(get_child_at<vit>(*state.child_, state.key_, state.index_), state);
214  }
215  private:
216  void do_from_config(typename maybe_const<vit, config>::type & cfg, typename get_variable_key_visitor::param_type state) const
217  {
218  state.type_ = state_named;
219  state.key_ = key_;
220  state.child_ = &cfg;
221  }
222  const std::string& key_;
223  };
224 
225  /// appens a [index] to the variable.
226  /// we only support from_named since [index][index2] or a.length[index] both doesn't make sense.
227  template<const variable_info_type vit>
228  class get_variable_index_visitor
229  : public variable_info_visitor<vit, void>
230  {
231  public:
232  get_variable_index_visitor(int n) : n_(n) {}
233  void from_named(typename get_variable_index_visitor::param_type state) const
234  {
235  state.index_ = n_;
236  resolve_negative_value(state.child_->child_count(state.key_), state.index_);
237  state.type_ = state_indexed;
238  }
239  private:
240  const int n_;
241  };
242 }
243 /// as... visitors
244 namespace
245 {
246  ///tries to convert it to an (maybe const) attribute value
247  template<const variable_info_type vit>
248  class as_skalar_visitor
249  : public variable_info_visitor_const<vit, typename maybe_const<vit, config::attribute_value>::type&>
250  {
251  public:
252  typename as_skalar_visitor::result_type from_named(typename as_skalar_visitor::param_type state) const
253  {
254  return (*state.child_)[state.key_];
255  }
256  ///Defined below for different cases.
257  typename as_skalar_visitor::result_type from_temporary(typename as_skalar_visitor::param_type state) const;
258  };
259  /// this type of value like '.length' are readonly values so we only support them for reading.
260  /// espacily we don't want to return non const references.
261  template<>
262  const config::attribute_value & as_skalar_visitor<vit_const>::from_temporary(as_skalar_visitor::param_type state) const
263  {
264  return state.temp_val_;
265  }
266  template<>
267  config::attribute_value & as_skalar_visitor<vit_create_if_not_existent>::from_temporary(as_skalar_visitor::param_type) const
268  {
270  }
271  template<>
272  config::attribute_value & as_skalar_visitor<vit_throw_if_not_existent>::from_temporary(as_skalar_visitor::param_type) const
273  {
275  }
276 
277  /// tries to convert to a (const) config&, unlike range based operation this also supports 'from_start'
278  /// Note: Currently getting the 'from_start' case here is impossible, because we always apply at least one string key.
279  template<const variable_info_type vit>
280  class as_container_visitor
281  : public variable_info_visitor_const<vit, typename maybe_const<vit, config>::type&>
282  {
283  public:
284  typename as_container_visitor::result_type from_named(typename as_container_visitor::param_type state) const
285  {
286  return get_child_at<vit>(*state.child_, state.key_, 0);
287  }
288  typename as_container_visitor::result_type from_start(typename as_container_visitor::param_type state) const
289  {
290  return *state.child_;
291  }
292  typename as_container_visitor::result_type from_indexed(typename as_container_visitor::param_type state) const
293  {
294  return get_child_at<vit>(*state.child_, state.key_, state.index_);
295  }
296  };
297 
298  // This currently isn't implemented as a range based operation because doing it on something like range
299  // 2-5 on vit_const if child_ has only 4 elements would be too hard to implement.
300  template<const variable_info_type vit>
301  class as_array_visitor
302  : public variable_info_visitor_const<vit, typename maybe_const<vit, config::child_itors>::type>
303  {
304  public:
305  typename as_array_visitor::result_type from_named(typename as_array_visitor::param_type state) const
306  {
307  return get_child_range(*state.child_, state.key_, 0, state.child_->child_count(state.key_));
308  }
309  typename as_array_visitor::result_type from_indexed(typename as_array_visitor::param_type state) const;
310  };
311  template<>
312  config::const_child_itors as_array_visitor<vit_const>::from_indexed(as_array_visitor::param_type state) const
313  {
314  if (int(state.child_->child_count(state.key_)) <= state.index_)
315  {
316  return get_child_range(non_empty_const_cfg, "_", 0, 1);
317  }
318  else
319  {
320  return get_child_range(*state.child_, state.key_, state.index_, 1);
321  }
322  }
323  template<>
324  config::child_itors as_array_visitor<vit_create_if_not_existent>::from_indexed(as_array_visitor::param_type state) const
325  {
326  //Ensure we have a config at the given explicit position.
327  get_child_at<vit_create_if_not_existent>(*state.child_, state.key_, state.index_);
328  return get_child_range(*state.child_, state.key_, state.index_, 1);
329  }
330  template<>
331  config::child_itors as_array_visitor<vit_throw_if_not_existent>::from_indexed(as_array_visitor::param_type state) const
332  {
333  //Ensure we have a config at the given explicit position.
334  get_child_at<vit_throw_if_not_existent>(*state.child_, state.key_, state.index_);
335  return get_child_range(*state.child_, state.key_, state.index_, 1);
336  }
337 }
338 /// range_based operations
339 namespace {
340 
341  /// @tparam THandler a function
342  /// (config& cfg, const std::string& name, int range_begin, int range_end) -> THandler::result_type
343  /// that does the actual work on the range of children of cfg with name name.
344  /// Note that currently this is only used by the insert/append/replace/merge operations
345  /// so vit is always vit_create_if_not_existent
346  template<const variable_info_type vit, typename THandler>
347  class as_range_visitor_base
348  : public variable_info_visitor_const<vit, typename THandler::result_type>
349  {
350  public:
351  as_range_visitor_base(const THandler& handler) : handler_(handler) {}
352  typename as_range_visitor_base::result_type from_named(typename as_range_visitor_base::param_type state) const
353  {
354  return handler_(*state.child_, state.key_, 0, state.child_->child_count(state.key_));
355  }
356  typename as_range_visitor_base::result_type from_indexed(typename as_range_visitor_base::param_type state) const
357  {
358  return this->handler_(*state.child_, state.key_, state.index_, state.index_ + 1);
359  }
360 
361  protected:
362  const THandler& handler_;
363  };
364 
365  /**
366  replaces the child in [startindex, endindex) with 'source'
367  'insert' and 'append' are subcases of this.
368  */
369  class replace_range_h
370  {
371  public:
372  typedef config::child_itors result_type;
373  replace_range_h(std::vector<config>& source) : datasource_(source) { }
374  result_type operator()(config& child, const std::string& key, int startindex, int endindex) const
375  {
376  assert(endindex - startindex >= 0);
377  if (endindex > 0)
378  {
379  //NOTE: currently this is nonly called from as_range_visitor_base<vit_create_if_not_existent>
380  //based on that assumption we use get_child_at<vit_create_if_not_existent> here
381  //instead of making this a template<typename vit> function/type
382  get_child_at<vit_create_if_not_existent>(child, key, endindex - 1);
383  }
384  int size_diff = datasource_.size() - (endindex - startindex);
385  //remove configs first
386  while(size_diff < 0)
387  {
388  child.remove_child(key, startindex);
389  ++size_diff;
390  }
391  size_t index = 0;
392  for(index = 0; index < static_cast<size_t>(size_diff); ++index)
393  {
394  child.add_child_at(key, config(), startindex + index).swap(datasource_[index]);
395  }
396  for(; index < datasource_.size(); ++index)
397  {
398  child.child(key, startindex + index).swap(datasource_[index]);
399  }
400  return get_child_range(child, key, startindex, datasource_.size());
401  }
402  private:
403  std::vector<config>& datasource_;
404  };
405 
406  class insert_range_h : replace_range_h
407  {
408  public:
409  typedef config::child_itors result_type;
410  insert_range_h(std::vector<config>& source) : replace_range_h(source) { }
411 
412  result_type operator()(config& child, const std::string& key, int startindex, int /*endindex*/) const
413  {
414  //insert == replace empty range with data.
415  return replace_range_h::operator()(child, key, startindex, startindex);
416  }
417  };
418 
419  class append_range_h : insert_range_h
420  {
421  public:
422  typedef config::child_itors result_type;
423  append_range_h(std::vector<config>& source) : insert_range_h(source) { }
424  result_type operator()(config& child, const std::string& key, int /*startindex*/, int /*endindex*/) const
425  {
426  //append == insert at end.
427  int inser_pos = child.child_count(key);
428  return insert_range_h::operator()(child, key, inser_pos, inser_pos /*ignored by insert_range_h*/);
429  }
430  };
431 
432  class merge_range_h
433  {
434  public:
435  typedef void result_type;
436  merge_range_h(std::vector<config>& source) : datasource_(source) { }
437  void operator()(config& child, const std::string& key, int startindex, int /*endindex*/) const
438  {
439  //the merge_with function accepts only configs so we convert vector -> config.
440  config datatemp;
441  //Add emtpy config to 'shift' the merge to startindex
442  for(int index = 0; index < startindex; ++index)
443  {
444  datatemp.add_child(key);
445  }
446  //move datasource_ -> datatemp
447  for(size_t index = 0; index < datasource_.size(); ++index)
448  {
449  datatemp.add_child(key).swap(datasource_[index]);
450  }
451  child.merge_with(datatemp);
452  }
453  private:
454  std::vector<config>& datasource_;
455  };
456 }//annonymous namespace end based operations
457 /// misc
458 namespace
459 {
460  template<const variable_info_type vit>
461  class clear_value_visitor
462  : public variable_info_visitor_const<vit, void>
463  {
464  public:
465  clear_value_visitor(bool only_tables) : only_tables_(only_tables) {}
466  void from_named(typename clear_value_visitor::param_type state) const
467  {
468  if(!only_tables_)
469  {
470  state.child_->remove_attribute(state.key_);
471  }
472  state.child_->clear_children(state.key_);
473  }
474  void from_indexed(typename clear_value_visitor::param_type state) const
475  {
476  state.child_->remove_child(state.key_, state.index_);
477  }
478  private:
479  bool only_tables_;
480  };
481 
482  template<const variable_info_type vit>
483  class exists_as_container_visitor
484  : public variable_info_visitor_const<vit, bool>
485  {
486  public:
487  typename exists_as_container_visitor::result_type from_named(typename exists_as_container_visitor::param_type state) const
488  {
489  return state.child_->has_child(state.key_);
490  }
491  typename exists_as_container_visitor::result_type from_indexed(typename exists_as_container_visitor::param_type state) const
492  {
493  return state.child_->child_count(state.key_) > static_cast<size_t>(state.index_);
494  }
495  typename exists_as_container_visitor::result_type from_start(typename exists_as_container_visitor::param_type) const
496  {
497  return true;
498  }
499  typename exists_as_container_visitor::result_type from_temporary(typename exists_as_container_visitor::param_type) const
500  {
501  return false;
502  }
503  };
504 }
505 
506 template<const variable_info_type vit>
508  : name_(varname)
509  , state_(vars)
510  , valid_(true)
511 {
512  try
513  {
514  this->calculate_value();
515  }
516  catch(const invalid_variablename_exception&)
517  {
518  this->valid_ = false;
519  }
520 }
521 
522 template<const variable_info_type vit>
524 {
525 }
526 
527 template<const variable_info_type vit>
529 {
530  // this->state_ is initialized in the constructor.
531  size_t previous_index = 0;
532  size_t name_size = this->name_.size();
533  for(size_t loop_index = 0; loop_index < name_size; loop_index++)
534  {
535  switch(this->name_[loop_index])
536  {
537  case '.':
538  case '[':
539  // '.', '[' mark the end of a string key.
540  // the result is oviously that '.' and '[' are
541  // treated equally so 'aaa.9].bbbb[zzz.uu.7]'
542  // is interpreted as 'aaa[9].bbbb.zzz.uu[7]'
543  // use is_valid_variable function for stricter variablename checking.
544  apply_visitor(get_variable_key_visitor<vit>(this->name_.substr(previous_index, loop_index-previous_index)), this->state_);
545  previous_index = loop_index + 1;
546  break;
547  case ']':
548  // ']' marks the end of an integer key.
549  apply_visitor(get_variable_index_visitor<vit>(parse_index(&this->name_[previous_index])), this->state_);
550  //after ']' we always expect a '.' or the end of the string
551  //ignore the next char which is a '.'
552  loop_index++;
553  if(loop_index < this->name_.length() && this->name_[loop_index] != '.')
554  {
556  }
557  previous_index = loop_index + 1;
558  break;
559  default:
560  break;
561  }
562  }
563  if(previous_index != this->name_.length() + 1)
564  {
565  // the string ended not with ']'
566  // in this case we still didn't add the key behind the last '.'
567  apply_visitor(get_variable_key_visitor<vit>(this->name_.substr(previous_index)), this->state_);
568  }
569 }
570 
571 template<const variable_info_type vit>
573 {
574  throw_on_invalid();
575  return this->state_.type_ == state_start || this->state_.type_ == state_indexed;
576 }
577 
578 template<const variable_info_type vit>
580 {
581  throw_on_invalid();
582  return apply_visitor(as_skalar_visitor<vit>(), this->state_);
583 }
584 
585 template<const variable_info_type vit>
587 {
588  throw_on_invalid();
589  return apply_visitor(as_container_visitor<vit>(), this->state_);
590 }
591 
592 template<const variable_info_type vit>
594 {
595  throw_on_invalid();
596  return apply_visitor(as_array_visitor<vit>(), this->state_);
597 }
598 
599 template<const variable_info_type vit>
601 {
602  if(!this->valid_)
603  {
605  }
606 }
607 
608 template<>
610 {
611  return "Cannot resolve variablename '" + this->name_ + "' for reading.";
612 }
613 
614 template<>
616 {
617  return "Cannot resolve variablename '" + this->name_ + "' for writing.";
618 }
619 
620 template<>
622 {
623  return "Cannot resolve variablename '" + this->name_ + "' for writing without creating new childs.";
624 }
625 
626 template<const variable_info_type vit>
627 void non_const_variable_info<vit>::clear(bool only_tables) const
628 {
629  this->throw_on_invalid();
630  return apply_visitor(clear_value_visitor<vit>(only_tables), this->state_);
631 }
632 
633 template<const variable_info_type vit>
635 {
636  this->throw_on_invalid();
637  return apply_visitor(as_range_visitor_base<vit,append_range_h>(append_range_h(childs)), this->state_);
638 }
639 
640 template<const variable_info_type vit>
642 {
643  this->throw_on_invalid();
644  return apply_visitor(as_range_visitor_base<vit,insert_range_h>(insert_range_h(childs)), this->state_);
645 }
646 
647 template<const variable_info_type vit>
649 {
650  this->throw_on_invalid();
651  return apply_visitor(as_range_visitor_base<vit,replace_range_h>(replace_range_h(childs)), this->state_);
652 }
653 
654 template<const variable_info_type vit>
655 void non_const_variable_info<vit>::merge_array(std::vector<config> childs) const
656 {
657  this->throw_on_invalid();
658  apply_visitor(as_range_visitor_base<vit,merge_range_h>(merge_range_h(childs)), this->state_);
659 }
660 
661 template<const variable_info_type vit>
663 {
664  this->throw_on_invalid();
665  return (this->state_.type_ == state_temporary) || ((this->state_.type_ == state_named) && this->state_.child_->has_attribute(this->state_.key_));
666 }
667 template<const variable_info_type vit>
669 {
670  this->throw_on_invalid();
671  return apply_visitor(exists_as_container_visitor<vit>(), this->state_);
672 }
673 
674 ///explicit instantiations
675 template class variable_info<vit_const>;
bool explicit_index() const
Doesn't throw.
variable_info_detail::maybe_const< vit, config::attribute_value >::type & as_scalar() const
might throw invalid_variablename_exception NOTE: If vit == vit_const, then the lifime of the returned...
#define DEFAULTHANDLER(name)
static thandler * handler
Definition: handler.cpp:60
variable_info_detail::maybe_const< vit, config::child_itors >::type as_array() const
might throw invalid_variablename_exception
void merge_array(std::vector< config > childs) const
merges might throw invalid_variablename_exception
bool exists_as_container() const
might throw invalid_variablename_exception
void clear(bool only_tables=false) const
clears the vale this object points to if only_tables = true it will not clear attribute values...
Extends variable_info with methods that can only be applied if vit != vit_const.
config::child_itors replace_array(std::vector< config > childs) const
GLuint const GLfloat * val
Definition: glew.h:2614
std::pair< const_child_iterator, const_child_iterator > const_child_itors
Definition: config.hpp:214
Variant for storing WML attributes.
Definition: config.hpp:223
expression_ptr key_
Definition: formula.cpp:435
void calculate_value()
variable_info(const std::string &varname, t_config &vars)
Doesn't throw.
static bool valid_id(const std::string &id)
Definition: config.cpp:498
void merge_with(const config &c)
Merge config 'c' into this config, overwriting this config's values.
Definition: config.cpp:1350
void swap(config &cfg)
Definition: config.cpp:1518
const GLdouble * v
Definition: glew.h:1359
GLuint start
Definition: glew.h:1221
const 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...
void remove_child(const std::string &key, unsigned index)
Definition: config.cpp:899
config & add_child(const std::string &key)
Definition: config.cpp:743
GLuint GLuint GLsizei count
Definition: glew.h:1221
GLuint res
Definition: glew.h:9258
variable_info_detail::maybe_const< vit, config >::type & as_container() const
might throw invalid_variablename_exception
GLuint index
Definition: glew.h:1782
std::pair< child_iterator, child_iterator > child_itors
Definition: config.hpp:213
Information on a WML variable.
unsigned child_count(const std::string &key) const
Definition: config.cpp:635
GLsizeiptr size
Definition: glew.h:1649
GLclampd n
Definition: glew.h:5903
config & child(const std::string &key, int n=0)
Returns the nth child with the given key, or a reference to an invalid config if there is none...
Definition: config.cpp:658
config::child_itors insert_array(std::vector< config > childs) const
std::string get_error_message() const
config::child_itors append_array(std::vector< config > childs) const
void throw_on_invalid() const
bool exists_as_attribute() const
might throw invalid_variablename_exception
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:83
GLsizei const GLcharARB ** string
Definition: glew.h:4503
GLsizei GLsizei GLchar * source
Definition: glew.h:1800
config & add_child_at(const std::string &key, const config &val, unsigned index)
Definition: config.cpp:773