Refactor variable_info implementation

First off, this doesn't touch the actual functionality of variable_info. Instead, it refactors how its all
put together:

* Comments have been cleaned up and improved.
* Formatting has been fixed.
* Class/type names have been improved.
* All info visitor and range helpers have been moved to a separate file.
* apply_visitor now takes a parameter pack and owns its own visitor object.
* as_range_visitor_base now takes a parameter pack and owns its own handler object
* The variable_info policy types have been split into their own helper classes and their respective functionality
  implemented with static functions. This makes it clearer exactly how each policy works.
* Simplified the use of type traits. The weird enable_if_non_const metastruct has been removed and a static_assert
  added to ensure variable_info_mutable is specialized correctly.
This commit is contained in:
Charles Dang 2017-05-14 07:07:53 +11:00
parent 4f888e2073
commit cdaa588eda
4 changed files with 849 additions and 698 deletions

View file

@ -14,663 +14,218 @@
See the COPYING file for more details.
*/
/**
* @file
* Manage WML-variables.
*/
#include "variable_info.hpp"
#include "game_config.hpp"
#include "config_assign.hpp"
#include "variable_info_private.hpp"
#include <stdexcept>
#include <utility>
using namespace variable_info_detail;
/// general helpers
namespace
namespace variable_info_implementation
{
/// TConfig is eigher 'config' or 'const config'
template<typename TConfig>
auto get_child_range(TConfig& cfg, const std::string& key, int start, int count) -> decltype(cfg.child_range(key))
{
auto res = cfg.child_range(key);
return { res.begin() + start, res.begin() + start + count};
}
void resolve_negative_value(int size, int& val)
{
if(val < 0)
{
val = size + val;
}
//val is still < 0? We don't accept!
if(val < 0)
{
throw invalid_variablename_exception();
}
}
template<const variable_info_type vit>
typename maybe_const<vit, config>::type& get_child_at(typename maybe_const<vit, config>::type& cfg, const std::string& key, int index = 0);
template<>
config& get_child_at<vit_create_if_not_existent>(config& cfg, const std::string& key, int index)
{
assert(index >= 0);
// the 'create_if_not_existent' logic.
while(static_cast<int>(cfg.child_count(key)) <= index)
{
cfg.add_child(key);
}
return cfg.child(key, index);
}
//helper variable for get_child_at<vit_const>
const config empty_const_cfg;
const config non_empty_const_cfg = config_of("_", config());
template<>
const config& get_child_at<vit_const>(const config& cfg, const std::string& key, int index)
{
assert(index >= 0);
//cfg.child_or_empty does not support index parameter
if(const config& child = cfg.child(key, index))
{
return child;
}
else
{
return empty_const_cfg;
}
}
template<>
config& get_child_at<vit_throw_if_not_existent>(config& cfg, const std::string& key, int index)
{
assert(index >= 0);
if(config& child = cfg.child(key, index))
{
return child;
}
else
{
throw invalid_variablename_exception();
}
}
template <typename TVal>
config::attribute_value attribute_value_of(const TVal& val)
{
config::attribute_value v;
v = val;
return v;
}
/// @param visitor
/// TVisitor should have 4 methods:
/// from_named if the variable ended with a .somename
/// from_indexed if the variable enden with .somename[someindex]
/// from_temporary if the variable is a readonly value (.somename.length)
/// from_start if the variablename was previously empty, this can only happen
/// during calculate_value()
/// TVisitor should derive from variable_info_visitor(_const) which makes default implementation for these (as not supported)
template <typename TVisitor>
typename TVisitor::result_type apply_visitor(const TVisitor& visitor, typename TVisitor::param_type state)
{
switch(state.type_)
{
case state_start:
return visitor.from_start(state);
case state_named:
return visitor.from_named(state);
case state_indexed:
return visitor.from_indexed(state);
case state_temporary:
return visitor.from_temporary(state);
}
throw std::range_error("Failed to convert the TVisitor::param_type type");
}
template <const variable_info_type vit, typename TResult>
class variable_info_visitor
{
public:
typedef TResult result_type;
typedef variable_info_state<vit>& param_type;
#define DEFAULTHANDLER(name) result_type name(param_type) const { throw invalid_variablename_exception(); }
DEFAULTHANDLER(from_start)
DEFAULTHANDLER(from_named)
DEFAULTHANDLER(from_indexed)
DEFAULTHANDLER(from_temporary)
#undef DEFAULTHANDLER
};
template <const variable_info_type vit, typename TResult>
class variable_info_visitor_const
{
public:
typedef TResult result_type;
typedef const variable_info_state<vit>& param_type;
#define DEFAULTHANDLER(name) result_type name(param_type) const { throw invalid_variablename_exception(); }
DEFAULTHANDLER(from_start)
DEFAULTHANDLER(from_named)
DEFAULTHANDLER(from_indexed)
DEFAULTHANDLER(from_temporary)
#undef DEFAULTHANDLER
};
}
/// calculate_value() helpers
namespace
/**
* Helper function to apply the result of a specified visitor to a variable_info object.
*
* @tparam V Visitor type.
* @tparam T Visitor argument parameter pack.
*
* @param state Info state (the actual variable data).
* @param args Arguments to forward to visitor constructor.
*
* @returns Visitor output in its specified type.
* @throws std::range_error If @a state has an invalid type_ field.
*/
template<typename V, typename... T>
typename V::result_t apply_visitor(typename V::param_t state, T&&... args)
{
/// Parses a ']' terminated string.
/// This is a important optimisation of lexical_cast_default
int parse_index(const char* index_str)
{
char* endptr;
int res = strtol(index_str, &endptr, 10);
static_assert(std::is_base_of<
info_visitor_base<
typename V::result_t,
typename std::remove_reference<typename V::param_t>::type>,
V>::value, "Invalid visitor type.");
if (*endptr != ']' || res > int(game_config::max_loop) || endptr == index_str)
{
throw invalid_variablename_exception();
}
return res;
// Create the visitor.
V visitor(std::forward<T>(args)...);
switch(state.type_) {
case state_start:
return visitor.from_start(state);
case state_named:
return visitor.from_named(state);
case state_indexed:
return visitor.from_indexed(state);
case state_temporary:
return visitor.from_temporary(state);
}
/// Adds a '.<key>' to the current variable
template<const variable_info_type vit>
class get_variable_key_visitor
: public variable_info_visitor<vit, void>
{
public:
get_variable_key_visitor(const std::string& key) : key_(key) {
if (!config::valid_id(key_)) {
throw invalid_variablename_exception();
}
}
void from_named(typename get_variable_key_visitor::param_type state) const
{
if(key_ == "length")
{
state.temp_val_ = state.child_->child_count(state.key_);
state.type_ = state_temporary;
return;
}
else
{
return do_from_config(get_child_at<vit>(*state.child_, state.key_, 0), state);
}
}
void from_start(typename get_variable_key_visitor::param_type state) const
{
return do_from_config(*state.child_, state);
}
void from_indexed(typename get_variable_key_visitor::param_type state) const
{
//we do not support aaa[0].length
return do_from_config(get_child_at<vit>(*state.child_, state.key_, state.index_), state);
}
private:
void do_from_config(typename maybe_const<vit, config>::type & cfg, typename get_variable_key_visitor::param_type state) const
{
state.type_ = state_named;
state.key_ = key_;
state.child_ = &cfg;
}
const std::string& key_;
};
/// appens a [index] to the variable.
/// we only support from_named since [index][index2] or a.length[index] both doesn't make sense.
template<const variable_info_type vit>
class get_variable_index_visitor
: public variable_info_visitor<vit, void>
{
public:
get_variable_index_visitor(int n) : n_(n) {}
void from_named(typename get_variable_index_visitor::param_type state) const
{
state.index_ = n_;
resolve_negative_value(state.child_->child_count(state.key_), state.index_);
state.type_ = state_indexed;
}
private:
const int n_;
};
throw std::range_error("Failed to convert the TVisitor::param_t type");
}
/// as... visitors
namespace
{
///tries to convert it to an (maybe const) attribute value
template<const variable_info_type vit>
class as_skalar_visitor
: public variable_info_visitor_const<vit, typename maybe_const<vit, config::attribute_value>::type&>
{
public:
typename as_skalar_visitor::result_type from_named(typename as_skalar_visitor::param_type state) const
{
return (*state.child_)[state.key_];
}
///Defined below for different cases.
typename as_skalar_visitor::result_type from_temporary(typename as_skalar_visitor::param_type state) const;
};
/// this type of value like '.length' are readonly values so we only support them for reading.
/// espacily we don't want to return non const references.
template<>
const config::attribute_value & as_skalar_visitor<vit_const>::from_temporary(as_skalar_visitor::param_type state) const
{
return state.temp_val_;
}
template<>
config::attribute_value & as_skalar_visitor<vit_create_if_not_existent>::from_temporary(as_skalar_visitor::param_type) const
{
throw invalid_variablename_exception();
}
template<>
config::attribute_value & as_skalar_visitor<vit_throw_if_not_existent>::from_temporary(as_skalar_visitor::param_type) const
{
throw invalid_variablename_exception();
}
} // end namespace variable_info_implementation
/// tries to convert to a (const) config&, unlike range based operation this also supports 'from_start'
/// Note: Currently getting the 'from_start' case here is impossible, because we always apply at least one string key.
template<const variable_info_type vit>
class as_container_visitor
: public variable_info_visitor_const<vit, typename maybe_const<vit, config>::type&>
{
public:
typename as_container_visitor::result_type from_named(typename as_container_visitor::param_type state) const
{
return get_child_at<vit>(*state.child_, state.key_, 0);
}
typename as_container_visitor::result_type from_start(typename as_container_visitor::param_type state) const
{
return *state.child_;
}
typename as_container_visitor::result_type from_indexed(typename as_container_visitor::param_type state) const
{
return get_child_at<vit>(*state.child_, state.key_, state.index_);
}
};
using namespace variable_info_implementation;
// This currently isn't implemented as a range based operation because doing it on something like range
// 2-5 on vit_const if child_ has only 4 elements would be too hard to implement.
template<const variable_info_type vit>
class as_array_visitor
: public variable_info_visitor_const<vit, typename maybe_const<vit, config::child_itors>::type>
{
public:
typename as_array_visitor::result_type from_named(typename as_array_visitor::param_type state) const
{
return get_child_range(*state.child_, state.key_, 0, state.child_->child_count(state.key_));
}
typename as_array_visitor::result_type from_indexed(typename as_array_visitor::param_type state) const;
};
template<>
config::const_child_itors as_array_visitor<vit_const>::from_indexed(as_array_visitor::param_type state) const
{
if (int(state.child_->child_count(state.key_)) <= state.index_)
{
return get_child_range(non_empty_const_cfg, "_", 0, 1);
}
else
{
return get_child_range(*state.child_, state.key_, state.index_, 1);
}
}
template<>
config::child_itors as_array_visitor<vit_create_if_not_existent>::from_indexed(as_array_visitor::param_type state) const
{
//Ensure we have a config at the given explicit position.
get_child_at<vit_create_if_not_existent>(*state.child_, state.key_, state.index_);
return get_child_range(*state.child_, state.key_, state.index_, 1);
}
template<>
config::child_itors as_array_visitor<vit_throw_if_not_existent>::from_indexed(as_array_visitor::param_type state) const
{
//Ensure we have a config at the given explicit position.
get_child_at<vit_throw_if_not_existent>(*state.child_, state.key_, state.index_);
return get_child_range(*state.child_, state.key_, state.index_, 1);
}
}
/// range_based operations
namespace {
/// @tparam THandler a function
/// (config& cfg, const std::string& name, int range_begin, int range_end) -> THandler::result_type
/// that does the actual work on the range of children of cfg with name name.
/// Note that currently this is only used by the insert/append/replace/merge operations
/// so vit is always vit_create_if_not_existent
template<const variable_info_type vit, typename THandler>
class as_range_visitor_base
: public variable_info_visitor_const<vit, typename THandler::result_type>
{
public:
as_range_visitor_base(const THandler& handler) : handler_(handler) {}
typename as_range_visitor_base::result_type from_named(typename as_range_visitor_base::param_type state) const
{
return handler_(*state.child_, state.key_, 0, state.child_->child_count(state.key_));
}
typename as_range_visitor_base::result_type from_indexed(typename as_range_visitor_base::param_type state) const
{
return this->handler_(*state.child_, state.key_, state.index_, state.index_ + 1);
}
protected:
const THandler& handler_;
};
/**
replaces the child in [startindex, endindex) with 'source'
'insert' and 'append' are subcases of this.
*/
class replace_range_h
{
public:
typedef config::child_itors result_type;
replace_range_h(std::vector<config>& source) : datasource_(source) { }
result_type operator()(config& child, const std::string& key, int startindex, int endindex) const
{
assert(endindex - startindex >= 0);
if (endindex > 0)
{
//NOTE: currently this is nonly called from as_range_visitor_base<vit_create_if_not_existent>
//based on that assumption we use get_child_at<vit_create_if_not_existent> here
//instead of making this a template<typename vit> function/type
get_child_at<vit_create_if_not_existent>(child, key, endindex - 1);
}
int size_diff = datasource_.size() - (endindex - startindex);
//remove configs first
while(size_diff < 0)
{
child.remove_child(key, startindex);
++size_diff;
}
size_t index = 0;
for(index = 0; index < static_cast<size_t>(size_diff); ++index)
{
child.add_child_at(key, config(), startindex + index).swap(datasource_[index]);
}
for(; index < datasource_.size(); ++index)
{
child.child(key, startindex + index).swap(datasource_[index]);
}
return get_child_range(child, key, startindex, datasource_.size());
}
private:
std::vector<config>& datasource_;
};
class insert_range_h : replace_range_h
{
public:
typedef config::child_itors result_type;
insert_range_h(std::vector<config>& source) : replace_range_h(source) { }
result_type operator()(config& child, const std::string& key, int startindex, int /*endindex*/) const
{
//insert == replace empty range with data.
return replace_range_h::operator()(child, key, startindex, startindex);
}
};
class append_range_h : insert_range_h
{
public:
typedef config::child_itors result_type;
append_range_h(std::vector<config>& source) : insert_range_h(source) { }
result_type operator()(config& child, const std::string& key, int /*startindex*/, int /*endindex*/) const
{
//append == insert at end.
int inser_pos = child.child_count(key);
return insert_range_h::operator()(child, key, inser_pos, inser_pos /*ignored by insert_range_h*/);
}
};
class merge_range_h
{
public:
typedef void result_type;
merge_range_h(std::vector<config>& source) : datasource_(source) { }
void operator()(config& child, const std::string& key, int startindex, int /*endindex*/) const
{
//the merge_with function accepts only configs so we convert vector -> config.
config datatemp;
//Add emtpy config to 'shift' the merge to startindex
for(int index = 0; index < startindex; ++index)
{
datatemp.add_child(key);
}
//move datasource_ -> datatemp
for(size_t index = 0; index < datasource_.size(); ++index)
{
datatemp.add_child(key).swap(datasource_[index]);
}
child.merge_with(datatemp);
}
private:
std::vector<config>& datasource_;
};
}//annonymous namespace end based operations
/// misc
namespace
{
template<const variable_info_type vit>
class clear_value_visitor
: public variable_info_visitor_const<vit, void>
{
public:
clear_value_visitor(bool only_tables) : only_tables_(only_tables) {}
void from_named(typename clear_value_visitor::param_type state) const
{
if(!only_tables_)
{
state.child_->remove_attribute(state.key_);
}
state.child_->clear_children(state.key_);
}
void from_indexed(typename clear_value_visitor::param_type state) const
{
state.child_->remove_child(state.key_, state.index_);
}
private:
bool only_tables_;
};
template<const variable_info_type vit>
class exists_as_container_visitor
: public variable_info_visitor_const<vit, bool>
{
public:
typename exists_as_container_visitor::result_type from_named(typename exists_as_container_visitor::param_type state) const
{
return state.child_->has_child(state.key_);
}
typename exists_as_container_visitor::result_type from_indexed(typename exists_as_container_visitor::param_type state) const
{
return state.child_->child_count(state.key_) > static_cast<size_t>(state.index_);
}
typename exists_as_container_visitor::result_type from_start(typename exists_as_container_visitor::param_type) const
{
return true;
}
typename exists_as_container_visitor::result_type from_temporary(typename exists_as_container_visitor::param_type) const
{
return false;
}
};
}
template<const variable_info_type vit>
variable_info<vit>::variable_info(const std::string& varname, config_var& vars)
template<typename V>
variable_info<V>::variable_info(const std::string& varname, maybe_const_t<config, V>& vars) NOEXCEPT
: name_(varname)
, state_(vars)
, valid_(true)
{
try
{
this->calculate_value();
}
catch(const invalid_variablename_exception&)
{
this->valid_ = false;
try {
calculate_value();
} catch(const invalid_variablename_exception&) {
valid_ = false;
}
}
template<const variable_info_type vit>
variable_info<vit>::~variable_info()
template<typename V>
void variable_info<V>::calculate_value()
{
}
size_t previous_index = 0, name_size = name_.size();
template<const variable_info_type vit>
void variable_info<vit>::calculate_value()
{
// this->state_ is initialized in the constructor.
size_t previous_index = 0;
size_t name_size = this->name_.size();
for(size_t loop_index = 0; loop_index < name_size; loop_index++)
{
switch(this->name_[loop_index])
{
for(size_t loop_index = 0; loop_index < name_size; loop_index++) {
switch(name_[loop_index]) {
case '.':
case '[':
// '.', '[' mark the end of a string key.
// the result is oviously that '.' and '[' are
// treated equally so 'aaa.9].bbbb[zzz.uu.7]'
// is interpreted as 'aaa[9].bbbb.zzz.uu[7]'
// use is_valid_variable function for stricter variablename checking.
apply_visitor(get_variable_key_visitor<vit>(this->name_.substr(previous_index, loop_index-previous_index)), this->state_);
/* '.' and '[' mark the end of a string key.
* The result is obviously that '.' and '[' are
* treated equally so 'aaa.9].bbbb[zzz.uu.7]'
* is interpreted as 'aaa[9].bbbb.zzz.uu[7]'
* Use is_valid_variable function for stricter variable name checking.
*/
apply_visitor<get_variable_key_visitor<V>>(
state_, name_.substr(previous_index, loop_index - previous_index));
previous_index = loop_index + 1;
break;
case ']':
// ']' marks the end of an integer key.
apply_visitor(get_variable_index_visitor<vit>(parse_index(&this->name_[previous_index])), this->state_);
//after ']' we always expect a '.' or the end of the string
//ignore the next char which is a '.'
apply_visitor<get_variable_index_visitor<V>>(state_, parse_index(&name_[previous_index]));
// After ']' we always expect a '.' or the end of the string
// Ignore the next char which is a '.'
loop_index++;
if(loop_index < this->name_.length() && this->name_[loop_index] != '.')
{
if(loop_index < name_.length() && name_[loop_index] != '.') {
throw invalid_variablename_exception();
}
previous_index = loop_index + 1;
break;
default:
break;
}
}
if(previous_index != this->name_.length() + 1)
{
// the string ended not with ']'
// in this case we still didn't add the key behind the last '.'
apply_visitor(get_variable_key_visitor<vit>(this->name_.substr(previous_index)), this->state_);
if(previous_index != name_.length() + 1) {
// The string didn't end with ']'
// In this case we still didn't add the key behind the last '.'
apply_visitor<get_variable_key_visitor<V>>(state_, name_.substr(previous_index));
}
}
template<const variable_info_type vit>
bool variable_info<vit>::explicit_index() const
template<typename V>
bool variable_info<V>::explicit_index() const NOEXCEPT
{
throw_on_invalid();
return this->state_.type_ == state_start || this->state_.type_ == state_indexed;
return state_.type_ == state_start || state_.type_ == state_indexed;
}
template<const variable_info_type vit>
typename maybe_const<vit, config::attribute_value>::type& variable_info<vit>::as_scalar() const
template<typename V>
maybe_const_t<config::attribute_value, V>& variable_info<V>::as_scalar() const
{
throw_on_invalid();
return apply_visitor(as_skalar_visitor<vit>(), this->state_);
return apply_visitor<as_scalar_visitor<V>>(state_);
}
template<const variable_info_type vit>
typename maybe_const<vit, config>::type& variable_info<vit>::as_container() const
template<typename V>
maybe_const_t<config, V>& variable_info<V>::as_container() const
{
throw_on_invalid();
return apply_visitor(as_container_visitor<vit>(), this->state_);
return apply_visitor<as_container_visitor<V>>(state_);
}
template<const variable_info_type vit>
typename maybe_const<vit, config::child_itors>::type variable_info<vit>::as_array() const
template<typename V>
maybe_const_t<config::child_itors, V> variable_info<V>::as_array() const
{
throw_on_invalid();
return apply_visitor(as_array_visitor<vit>(), this->state_);
return apply_visitor<as_array_visitor<V>>(state_);
}
template<const variable_info_type vit>
void variable_info<vit>::throw_on_invalid() const
template<typename V>
void variable_info<V>::throw_on_invalid() const
{
if(!this->valid_)
{
if(!valid_) {
throw invalid_variablename_exception();
}
}
template<>
std::string variable_info<vit_const>::get_error_message() const
template<typename V>
std::string variable_info<V>::get_error_message() const
{
return "Cannot resolve variablename '" + this->name_ + "' for reading.";
return V::error_message(name_);
}
template<>
std::string variable_info<vit_create_if_not_existent>::get_error_message() const
template<typename V>
bool variable_info<V>::exists_as_attribute() const
{
return "Cannot resolve variablename '" + this->name_ + "' for writing.";
throw_on_invalid();
return (state_.type_ == state_temporary)
|| ((state_.type_ == state_named) && state_.child_->has_attribute(state_.key_));
}
template<>
std::string variable_info<vit_throw_if_not_existent>::get_error_message() const
template<typename V>
bool variable_info<V>::exists_as_container() const
{
return "Cannot resolve variablename '" + this->name_ + "' for writing without creating new childs.";
throw_on_invalid();
return apply_visitor<exists_as_container_visitor<V>>(state_);
}
template<const variable_info_type vit>
void non_const_variable_info<vit>::clear(bool only_tables) const
template<typename V>
void variable_info_mutable<V>::clear(bool only_tables) const
{
this->throw_on_invalid();
return apply_visitor(clear_value_visitor<vit>(only_tables), this->state_);
return apply_visitor<clear_value_visitor<V>>(this->state_, only_tables);
}
template<const variable_info_type vit>
config::child_itors non_const_variable_info<vit>::append_array(std::vector<config> childs) const
/**
* In order to allow the appropriate 'children' argument to forward through to the appropriate
* ctor of type T in as_range_visitor_base via apply_visitor, we need to specify the argument
* type as an rvalue reference to lvalue reference in order to collapse to an lvalue reference.
*
* Using a convenient template alias for convenience.
*/
template<typename V, typename T>
using range_visitor_wrapper = as_range_visitor_base<V, T, std::vector<config>&>;
template<typename V>
config::child_itors variable_info_mutable<V>::append_array(std::vector<config> children) const
{
this->throw_on_invalid();
return apply_visitor(as_range_visitor_base<vit,append_range_h>(append_range_h(childs)), this->state_);
return apply_visitor<range_visitor_wrapper<V, append_range_h>>(this->state_, children);
}
template<const variable_info_type vit>
config::child_itors non_const_variable_info<vit>::insert_array(std::vector<config> childs) const
template<typename V>
config::child_itors variable_info_mutable<V>::insert_array(std::vector<config> children) const
{
this->throw_on_invalid();
return apply_visitor(as_range_visitor_base<vit,insert_range_h>(insert_range_h(childs)), this->state_);
return apply_visitor<range_visitor_wrapper<V, insert_range_h>>(this->state_, children);
}
template<const variable_info_type vit>
config::child_itors non_const_variable_info<vit>::replace_array(std::vector<config> childs) const
template<typename V>
config::child_itors variable_info_mutable<V>::replace_array(std::vector<config> children) const
{
this->throw_on_invalid();
return apply_visitor(as_range_visitor_base<vit,replace_range_h>(replace_range_h(childs)), this->state_);
return apply_visitor<range_visitor_wrapper<V, replace_range_h>>(this->state_, children);
}
template<const variable_info_type vit>
void non_const_variable_info<vit>::merge_array(std::vector<config> childs) const
template<typename V>
void variable_info_mutable<V>::merge_array(std::vector<config> children) const
{
this->throw_on_invalid();
apply_visitor(as_range_visitor_base<vit,merge_range_h>(merge_range_h(childs)), this->state_);
apply_visitor<range_visitor_wrapper<V, merge_range_h>>(this->state_, children);
}
template<const variable_info_type vit>
bool variable_info<vit>::exists_as_attribute() const
{
this->throw_on_invalid();
return (this->state_.type_ == state_temporary) || ((this->state_.type_ == state_named) && this->state_.child_->has_attribute(this->state_.key_));
}
template<const variable_info_type vit>
bool variable_info<vit>::exists_as_container() const
{
this->throw_on_invalid();
return apply_visitor(exists_as_container_visitor<vit>(), this->state_);
}
// Force compilation of the following template instantiations
template class variable_info<const vi_policy_const>;
template class variable_info<vi_policy_create>;
template class variable_info<vi_policy_throw>;
///explicit instantiations
template class variable_info<vit_const>;
template class variable_info<vit_create_if_not_existent>;
template class variable_info<vit_throw_if_not_existent>;
template class non_const_variable_info<vit_create_if_not_existent>;
template class non_const_variable_info<vit_throw_if_not_existent>;
template class variable_info_mutable<vi_policy_create>;
template class variable_info_mutable<vi_policy_throw>;

View file

@ -16,98 +16,136 @@
#pragma once
/** Information on a WML variable. */
#include <string>
#include "config.hpp"
#include "variable_info_detail.hpp"
#include <string>
#include <type_traits>
class invalid_variablename_exception : public std::exception
{
public:
invalid_variablename_exception() : std::exception() {}
const char* what() const NOEXCEPT
{
return "invalid_variablename_exception";
}
};
template<const variable_info_detail::variable_info_type vit>
// NOTE: the detail file needs invalid_variablename_exception to be available,
// so include this after declaring it.
#include "variable_info_detail.hpp"
/** Information on a WML variable. */
template<typename V>
class variable_info
{
public:
variable_info(const std::string& varname, maybe_const_t<config, V>& vars) NOEXCEPT;
typedef typename variable_info_detail::maybe_const<vit,config>::type config_var;
/// Doesn't throw
variable_info(const std::string& varname, config_var& vars);
~variable_info();
std::string get_error_message() const;
/// Doesn't throw
bool explicit_index() const;
/// might throw invalid_variablename_exception
bool explicit_index() const NOEXCEPT;
/** @throws invalid_variablename_exception */
bool exists_as_attribute() const;
/// might throw invalid_variablename_exception
/** @throws invalid_variablename_exception */
bool exists_as_container() const;
/**
might throw invalid_variablename_exception
NOTE:
If vit == vit_const, then the lifime of the returned const attribute_value& might end with the lifetime of this object.
*/
typename variable_info_detail::maybe_const<vit, config::attribute_value>::type &as_scalar() const;
/// might throw invalid_variablename_exception
typename variable_info_detail::maybe_const<vit, config>::type & as_container() const;
/// might throw invalid_variablename_exception
typename variable_info_detail::maybe_const<vit, config::child_itors>::type as_array() const; //range may be empty
* If instantiated with vi_policy_const, the lifetime of the returned
* const attribute_value reference might end with the lifetime of this object.
*
* @throws invalid_variablename_exception
*/
maybe_const_t<config::attribute_value, V>& as_scalar() const;
/**
* If instantiated with vi_policy_const, the lifetime of the returned
* const attribute_value reference might end with the lifetime of this object.
*
* @throws invalid_variablename_exception
*/
maybe_const_t<config, V>& as_container() const;
/**
* If instantiated with vi_policy_const, the lifetime of the returned
* const attribute_value reference might end with the lifetime of this object.
*
* Range may be empty
* @throws invalid_variablename_exception
*/
maybe_const_t<config::child_itors, V> as_array() const;
protected:
std::string name_;
variable_info_detail::variable_info_state<vit> state_;
void throw_on_invalid() const;
variable_info_implementation::variable_info_state<V> state_;
bool valid_;
void throw_on_invalid() const;
void calculate_value();
};
/// Extends variable_info with methods that can only be applied if vit != vit_const
template<const variable_info_detail::variable_info_type vit>
class non_const_variable_info : public variable_info<vit>, variable_info_detail::enable_if_non_const<vit>::type
/**
* Additional functionality for a non-const variable_info.
* @todo: should these functions take a reference?
*/
template<typename V>
class variable_info_mutable : public variable_info<V>
{
public:
non_const_variable_info(const std::string& name, config& game_vars) : variable_info<vit>(name, game_vars) {}
~non_const_variable_info() {}
variable_info_mutable(const std::string& name, config& game_vars)
: variable_info<V>(name, game_vars)
{
static_assert(!std::is_same<
variable_info_implementation::vi_policy_const, typename std::remove_const<V>::type>::value,
"variable_info_mutable cannot be specialized with 'vi_policy_const'"
);
}
/// clears the vale this object points to
/// if only_tables = true it will not clear attribute values.
/// might throw invalid_variablename_exception
/**
* @returns The new appended range.
* @throws invalid_variablename_exception
*/
config::child_itors append_array(std::vector<config> children) const;
/**
* @returns The new inserted range.
* @throws invalid_variablename_exception
*/
config::child_itors insert_array(std::vector<config> children) const;
/**
* @returns The new range.
* @throws invalid_variablename_exception
*/
config::child_itors replace_array(std::vector<config> children) const;
/**
* @throws invalid_variablename_exception
*/
void merge_array(std::vector<config> children) const;
/**
* Clears the value this object points to.
*
* @param only_tables If true, will not clear attribute values.
* @throws invalid_variablename_exception
*/
void clear(bool only_tables = false) const;
// the following 4 functions are used by [set_variables]
// they destroy the passed vector. (make it empty).
/// @return: the new appended range
/// might throw invalid_variablename_exception
config::child_itors append_array(std::vector<config> childs) const;
/// @return: the new inserted range
/// might throw invalid_variablename_exception
config::child_itors insert_array(std::vector<config> childs) const;
/// @return: the new range
/// might throw invalid_variablename_exception
config::child_itors replace_array(std::vector<config> childs) const;
/// merges
/// might throw invalid_variablename_exception
void merge_array(std::vector<config> childs) const;
};
/** 'Create if nonexistent' access. */
using variable_access_create = variable_info_mutable<variable_info_implementation::vi_policy_create>;
/** 'Throw if nonexistent' access. */
using variable_access_throw = variable_info_mutable<variable_info_implementation::vi_policy_throw>;
/**
this variable accessor will create a childtable when resolving name if it doesn't exist yet.
*/
typedef non_const_variable_info<variable_info_detail::vit_create_if_not_existent> variable_access_create;
/**
this variable accessor will throw an exception when trying to access a non existent table.
Note that the other types can throw too if name is invlid like '..[[[a'.
*/
typedef non_const_variable_info<variable_info_detail::vit_throw_if_not_existent> variable_access_throw;
/**
this variable accessor is takes a const reference and is guaranteed to not change the config.
*/
typedef variable_info<variable_info_detail::vit_const> variable_access_const;
* Read-only access.
*
* NOTE: in order to easily mark certain types in this specialization as const we specify
* the policy as const here. This allows the use of const_clone.
*/
using variable_access_const = variable_info<const variable_info_implementation::vi_policy_const>;

View file

@ -16,80 +16,142 @@
#pragma once
#include <string>
#include "config.hpp"
#include "utils/const_clone.hpp"
namespace variable_info_detail
#include <string>
#include <type_traits>
namespace variable_info_implementation
{
enum variable_info_type {vit_const, vit_create_if_not_existent, vit_throw_if_not_existent, };
enum variable_info_state_type {
state_start = 0, // for internal use
// only used at the 'starting_pos' of the variable_info::calculate_value algorithm
state_named, // the result of .someval this can eigher man an attribute value or an
// child range
state_indexed, // the result of .someval[index] this is never an attribute value,
// this is always a single config.
state_temporary, // the result of .length this value can never be written, it can only be read.
/**
* The variable_info policy classes.
*
* Each of these classes describes a different behavior for reading data from a variable
* and should implement two functions:
*
* - get_child_at Describes the desired behavior when reading variable info.
* - error_message Error message regarding policy behavior.
*/
};
//Special case of std::enable_if
template<const variable_info_type vit>
struct enable_if_non_const
/** Takes a const reference and is guaranteed to not change the config. */
class vi_policy_const
{
public:
static const config& get_child_at(const config& cfg, const std::string& key, int index)
{
typedef enable_if_non_const<vit> type;
};
template<>
struct enable_if_non_const<vit_const>
{
};
template<const variable_info_type vit, typename T>
struct maybe_const
{
typedef T type;
};
template <class T>
struct maybe_const<vit_const, T>
{
typedef const T type;
};
template <>
struct maybe_const<vit_const, config::child_itors>
{
typedef config::const_child_itors type;
};
template<const variable_info_type vit>
struct variable_info_state
{
typedef typename maybe_const<vit,config>::type child_t;
variable_info_state(child_t& vars)
: child_(&vars)
, key_()
, index_(0)
, temp_val_()
, type_(state_start)
{
child_ = &vars;
assert(index >= 0);
// cfg.child_or_empty does not support index parameter
if(const config& child = cfg.child(key, index)) {
return child;
}
// The meaning of the following 3 depends on 'type_', but usualy the case is:
// the current config is child_->child_at(key_, index_).
child_t* child_;
std::string key_;
int index_;
static const config empty_const_cfg;
return empty_const_cfg;
}
// If we have a temporary value like .length
// Then we store the result here.
config::attribute_value temp_val_;
static std::string error_message(const std::string& name)
{
return "Cannot resolve variable '" + name + "' for reading.";
}
};
// See the definition of 'variable_info_state_type'
variable_info_state_type type_;
};
}
/** Creates a child table when resolving name if it doesn't exist yet. */
class vi_policy_create
{
public:
static config& get_child_at(config& cfg, const std::string& key, int index)
{
assert(index >= 0);
// the 'create_if_not_existent' logic.
while(static_cast<int>(cfg.child_count(key)) <= index) {
cfg.add_child(key);
}
return cfg.child(key, index);
}
static std::string error_message(const std::string& name)
{
return "Cannot resolve variable '" + name + "' for writing.";
}
};
/**
* Will throw an exception when trying to access a nonexistent table.
* Note that the other types can throw too if name is invlid, such as '..[[[a'.
*/
class vi_policy_throw
{
public:
static config& get_child_at(config& cfg, const std::string& key, int index)
{
assert(index >= 0);
if(config& child = cfg.child(key, index)) {
return child;
}
throw invalid_variablename_exception();
}
static std::string error_message(const std::string& name)
{
return "Cannot resolve variable '" + name + "' for writing without creating new children.";
}
};
// ==================================================================
// Other implementation details.
// ==================================================================
template<typename T, typename V>
struct maybe_const : public utils::const_clone<T, V>
{
// Meta type aliases provided by const_clone
};
template<>
struct maybe_const<config::child_itors, const vi_policy_const>
{
using type = config::const_child_itors;
};
enum variable_info_state_type {
state_start = 0, /**< Represents the initial variable state before processing. */
state_named, /**< The result of .someval. This can either mean an attribute value or a child range. */
state_indexed, /**< The result of .someval[index]. This is never an attribute value and is always a single config. */
state_temporary, /**< The result of .length. This value can never be written, it can only be read. */
};
template<typename V>
struct variable_info_state
{
using child_t = typename maybe_const<config, V>::type;
variable_info_state(child_t& vars)
: child_(&vars)
, key_()
, index_(0)
, temp_val_()
, type_(state_start)
{
child_ = &vars;
}
// The meaning of the following 3 depends on 'type_', but the current config is usually
// child_->child_at(key_, index_).
child_t* child_;
std::string key_;
int index_;
// If we have a temporary value like .length we store the result here.
config::attribute_value temp_val_;
// See @ref variable_info_state_type
variable_info_state_type type_;
};
} // end namespace variable_info_implementation
/** Helper template alias for maybe_const, defined at global scope for convenience. */
template<typename T, typename V>
using maybe_const_t = typename variable_info_implementation::maybe_const<T, V>::type;

View file

@ -0,0 +1,496 @@
/*
Copyright (C) 2003 by David White <dave@whitevine.net>
Copyright (C) 2005 - 2017 by Philippe Plantier <ayin@anathas.org>
Part of the Battle for Wesnoth Project http://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.
*/
#pragma once
#include "config_assign.hpp"
#include "game_config.hpp"
#include <stdexcept>
namespace variable_info_implementation
{
// ==================================================================
// General helper functions
// ==================================================================
// TConfig is either 'config' or 'const config'
template<typename TConfig>
auto get_child_range(TConfig& cfg, const std::string& key, int start, int count) -> decltype(cfg.child_range(key))
{
auto res = cfg.child_range(key);
return {res.begin() + start, res.begin() + start + count};
}
void resolve_negative_value(int size, int& val)
{
if(val < 0) {
val = size + val;
}
// val is still < 0? We don't accept!
if(val < 0) {
throw invalid_variablename_exception();
}
}
const config non_empty_const_cfg = config_of("_", config());
/**
* Parses a ']' terminated string.
* This is a important optimization of lexical_cast_default
*/
int parse_index(const char* index_str)
{
char* endptr;
int res = strtol(index_str, &endptr, 10);
if(*endptr != ']' || res > int(game_config::max_loop) || endptr == index_str) {
throw invalid_variablename_exception();
}
return res;
}
// ==================================================================
// Visitor interface
// ==================================================================
/**
* Visitor base class.
*
* This provides the interface for the main functions each visitor can implement. The default implementation of
* each function simply throws @ref invalid_variablename_exception.
*
* This class also provides two type aliases corresponding to the function return value and argument types.
* Note that visitors shouldn't inherit from this directly and instead use the @ref info_visitor and
* @ref info_visitor_const wrappers, since both fully specify the parameter type.
*
* @tparam R Return value type.
* @tparam P Argument type.
*/
template<typename R, typename P>
class info_visitor_base
{
public:
using result_t = R;
using param_t = P&;
#define DEFAULTHANDLER(name) \
result_t name(param_t) const \
{ \
throw invalid_variablename_exception(); \
}
/** For use if the variable name was previously empty. This can only happen during calculate_value. */
DEFAULTHANDLER(from_start)
/** For use if the variable ended with a .somename. */
DEFAULTHANDLER(from_named)
/** For use if the variable ended with .somename[someindex]. */
DEFAULTHANDLER(from_indexed)
/** For use if the variable is a readonly value (.somename.length). */
DEFAULTHANDLER(from_temporary)
#undef DEFAULTHANDLER
};
template<typename V, typename TResult>
using info_visitor = info_visitor_base<TResult, variable_info_state<V>>;
template<typename V, typename TResult>
using info_visitor_const = info_visitor_base<TResult, const variable_info_state<V>>;
/** Adds a '.<key>' to the current variable. */
template<typename V>
class get_variable_key_visitor : public info_visitor<V, void>
{
public:
// Import typedefs from base class.
using param_t = typename get_variable_key_visitor::param_t;
get_variable_key_visitor(const std::string& key)
: key_(key)
{
if(!config::valid_id(key_)) {
throw invalid_variablename_exception();
}
}
void from_named(param_t state) const
{
if(key_ == "length") {
state.temp_val_ = state.child_->child_count(state.key_);
state.type_ = state_temporary;
return;
}
return do_from_config(V::get_child_at(*state.child_, state.key_, 0), state);
}
void from_start(param_t state) const
{
return do_from_config(*state.child_, state);
}
void from_indexed(param_t state) const
{
// We do not support aaa[0].length
return do_from_config(V::get_child_at(*state.child_, state.key_, state.index_), state);
}
private:
void do_from_config(maybe_const_t<config, V>& cfg, param_t state) const
{
state.type_ = state_named;
state.key_ = key_;
state.child_ = &cfg;
}
const std::string& key_;
};
/**
* Appends an [index] to the variable.
* We only support from_named since [index][index2] or a.length[index] both don't make sense.
*/
template<typename V>
class get_variable_index_visitor : public info_visitor<V, void>
{
public:
get_variable_index_visitor(int n)
: n_(n)
{
}
void from_named(typename get_variable_index_visitor::param_t state) const
{
state.index_ = n_;
resolve_negative_value(state.child_->child_count(state.key_), state.index_);
state.type_ = state_indexed;
}
private:
const int n_;
};
/** Tries to convert it to an (maybe const) attribute value. */
template<typename V>
class as_scalar_visitor : public info_visitor_const<V, maybe_const_t<config::attribute_value, V>&>
{
public:
// Import typedefs from base class.
using result_t = typename as_scalar_visitor::result_t;
using param_t = typename as_scalar_visitor::param_t;
result_t from_named(param_t state) const
{
return (*state.child_)[state.key_];
}
/**
* Only implemented for read-only variable_info. Other types use the default throw implementation.
*/
result_t from_temporary(param_t /*state*/) const
{
throw invalid_variablename_exception();
}
};
/**
* Values like '.length' are readonly so we only support reading them, especially since we don't
* want to return non-const references.
*/
template<>
const config::attribute_value& as_scalar_visitor<const vi_policy_const>::from_temporary(
as_scalar_visitor::param_t state) const
{
return state.temp_val_;
}
/**
* Tries to convert to a [const] config&. Unlike range based operation this also supports 'from_start'.
* NOTE: Currently getting the 'from_start' case here is impossible, because we always apply at least one string key.
*/
template<typename V>
class as_container_visitor : public info_visitor_const<V, maybe_const_t<config, V>&>
{
public:
// Import typedefs from base class.
using result_t = typename as_container_visitor::result_t;
using param_t = typename as_container_visitor::param_t;
result_t from_named(param_t state) const
{
return V::get_child_at(*state.child_, state.key_, 0);
}
result_t from_start(param_t state) const
{
return *state.child_;
}
result_t from_indexed(param_t state) const
{
return V::get_child_at(*state.child_, state.key_, state.index_);
}
};
/**
* This currently isn't implemented as a range-based operation because doing it on something like range
* 2-5 on vi_policy_const if child_ has only 4 elements would be too hard to implement.
*/
template<typename V>
class as_array_visitor : public info_visitor_const<V, maybe_const_t<config::child_itors, V>>
{
public:
// Import typedefs from base class.
using result_t = typename as_array_visitor::result_t;
using param_t = typename as_array_visitor::param_t;
result_t from_named(param_t state) const
{
return get_child_range(*state.child_, state.key_, 0, state.child_->child_count(state.key_));
}
result_t from_indexed(param_t state) const
{
// Ensure we have a config at the given explicit position.
V::get_child_at(*state.child_, state.key_, state.index_);
return get_child_range(*state.child_, state.key_, state.index_, 1);
}
};
template<>
config::const_child_itors as_array_visitor<const vi_policy_const>::from_indexed(as_array_visitor::param_t state) const
{
if(int(state.child_->child_count(state.key_)) <= state.index_) {
return get_child_range(non_empty_const_cfg, "_", 0, 1);
}
return get_child_range(*state.child_, state.key_, state.index_, 1);
}
/**
* @tparam THandler Handler type. Should implement an operator() with the signature:
* '(config&, const std::string&, int, int) -> THandler::result_t'
*
* That does the actual work on the range of children of cfg with name 'name'.
* Note this is currently only used by the insert/append/replace/merge operations, so V is always
* vi_policy_create.
*/
template<typename V, typename THandler, typename... T>
class as_range_visitor_base : public info_visitor_const<V, typename THandler::result_t>
{
public:
// Import typedefs from base class.
using result_t = typename as_range_visitor_base::result_t;
using param_t = typename as_range_visitor_base::param_t;
as_range_visitor_base(T&&... args)
: handler_(std::forward<T>(args)...)
{
}
result_t from_named(param_t state) const
{
return handler_(*state.child_, state.key_, 0, state.child_->child_count(state.key_));
}
result_t from_indexed(param_t state) const
{
return handler_(*state.child_, state.key_, state.index_, state.index_ + 1);
}
protected:
THandler handler_;
};
template<typename V>
class clear_value_visitor : public info_visitor_const<V, void>
{
public:
// Import typedefs from base class.
using param_t = typename clear_value_visitor::param_t;
clear_value_visitor(bool only_tables)
: only_tables_(only_tables)
{
}
void from_named(param_t state) const
{
if(!only_tables_) {
state.child_->remove_attribute(state.key_);
}
state.child_->clear_children(state.key_);
}
void from_indexed(param_t state) const
{
state.child_->remove_child(state.key_, state.index_);
}
private:
bool only_tables_;
};
template<typename V>
class exists_as_container_visitor : public info_visitor_const<V, bool>
{
public:
// Import typedefs from base class.
using param_t = typename exists_as_container_visitor::param_t;
bool from_named(param_t state) const
{
return state.child_->has_child(state.key_);
}
bool from_indexed(param_t state) const
{
return state.child_->child_count(state.key_) > static_cast<size_t>(state.index_);
}
bool from_start(param_t) const
{
return true;
}
bool from_temporary(param_t) const
{
return false;
}
};
// ==================================================================
// Range manipulation interface
// ==================================================================
/**
* Replaces the child in [startindex, endindex) with 'source'
* 'insert' and 'append' are subcases of this.
*/
class replace_range_h
{
public:
typedef config::child_itors result_t;
replace_range_h(std::vector<config>& source)
: datasource_(source)
{
}
result_t operator()(config& child, const std::string& key, int startindex, int endindex) const
{
assert(endindex - startindex >= 0);
if(endindex > 0) {
// NOTE: currently this is only called from as_range_visitor_base<vi_policy_create>
// Based on that assumption we use vi_policy_create::get_child_at here instead of making this
// a class template.
vi_policy_create::get_child_at(child, key, endindex - 1);
}
int size_diff = datasource_.size() - (endindex - startindex);
// remove configs first
while(size_diff < 0) {
child.remove_child(key, startindex);
++size_diff;
}
size_t index = 0;
for(index = 0; index < static_cast<size_t>(size_diff); ++index) {
child.add_child_at(key, config(), startindex + index).swap(datasource_[index]);
}
for(; index < datasource_.size(); ++index) {
child.child(key, startindex + index).swap(datasource_[index]);
}
return get_child_range(child, key, startindex, datasource_.size());
}
private:
std::vector<config>& datasource_;
};
class insert_range_h : replace_range_h
{
public:
typedef config::child_itors result_t;
insert_range_h(std::vector<config>& source)
: replace_range_h(source)
{
}
result_t operator()(config& child, const std::string& key, int startindex, int /*endindex*/) const
{
// insert == replace empty range with data.
return replace_range_h::operator()(child, key, startindex, startindex);
}
};
class append_range_h : insert_range_h
{
public:
typedef config::child_itors result_t;
append_range_h(std::vector<config>& source)
: insert_range_h(source)
{
}
result_t operator()(config& child, const std::string& key, int /*startindex*/, int /*endindex*/) const
{
// append == insert at end.
int insert_pos = child.child_count(key);
return insert_range_h::operator()(child, key, insert_pos, insert_pos /*ignored by insert_range_h*/);
}
};
class merge_range_h
{
public:
typedef void result_t;
merge_range_h(std::vector<config>& source)
: datasource_(source)
{
}
void operator()(config& child, const std::string& key, int startindex, int /*endindex*/) const
{
// The merge_with function only accepts configs so we convert vector -> config.
config datatemp;
// Add emtpy config to 'shift' the merge to startindex
for(int index = 0; index < startindex; ++index) {
datatemp.add_child(key);
}
// move datasource_ -> datatemp
for(size_t index = 0; index < datasource_.size(); ++index) {
datatemp.add_child(key).swap(datasource_[index]);
}
child.merge_with(datatemp);
}
private:
std::vector<config>& datasource_;
};
} // end namespace variable_info_implementation