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. See the COPYING file for more details.
*/ */
/**
* @file
* Manage WML-variables.
*/
#include "variable_info.hpp" #include "variable_info.hpp"
#include "game_config.hpp" #include "variable_info_private.hpp"
#include "config_assign.hpp"
#include <stdexcept> #include <utility>
using namespace variable_info_detail; namespace variable_info_implementation
/// general helpers
namespace
{ {
/// TConfig is eigher 'config' or 'const config' /**
template<typename TConfig> * Helper function to apply the result of a specified visitor to a variable_info object.
auto get_child_range(TConfig& cfg, const std::string& key, int start, int count) -> decltype(cfg.child_range(key)) *
{ * @tparam V Visitor type.
auto res = cfg.child_range(key); * @tparam T Visitor argument parameter pack.
return { res.begin() + start, res.begin() + start + count}; *
} * @param state Info state (the actual variable data).
* @param args Arguments to forward to visitor constructor.
void resolve_negative_value(int size, int& val) *
{ * @returns Visitor output in its specified type.
if(val < 0) * @throws std::range_error If @a state has an invalid type_ field.
{ */
val = size + val; template<typename V, typename... T>
} typename V::result_t apply_visitor(typename V::param_t state, T&&... args)
//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
{ {
/// Parses a ']' terminated string. static_assert(std::is_base_of<
/// This is a important optimisation of lexical_cast_default info_visitor_base<
int parse_index(const char* index_str) typename V::result_t,
{ typename std::remove_reference<typename V::param_t>::type>,
char* endptr; V>::value, "Invalid visitor type.");
int res = strtol(index_str, &endptr, 10);
if (*endptr != ']' || res > int(game_config::max_loop) || endptr == index_str) // Create the visitor.
{ V visitor(std::forward<T>(args)...);
throw invalid_variablename_exception();
} switch(state.type_) {
return res; 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 throw std::range_error("Failed to convert the TVisitor::param_t type");
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_;
};
} }
/// as... visitors } // end namespace variable_info_implementation
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();
}
/// tries to convert to a (const) config&, unlike range based operation this also supports 'from_start' using namespace variable_info_implementation;
/// 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_);
}
};
// This currently isn't implemented as a range based operation because doing it on something like range template<typename V>
// 2-5 on vit_const if child_ has only 4 elements would be too hard to implement. variable_info<V>::variable_info(const std::string& varname, maybe_const_t<config, V>& vars) NOEXCEPT
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)
: name_(varname) : name_(varname)
, state_(vars) , state_(vars)
, valid_(true) , valid_(true)
{ {
try try {
{ calculate_value();
this->calculate_value(); } catch(const invalid_variablename_exception&) {
} valid_ = false;
catch(const invalid_variablename_exception&)
{
this->valid_ = false;
} }
} }
template<const variable_info_type vit> template<typename V>
variable_info<vit>::~variable_info() void variable_info<V>::calculate_value()
{ {
} size_t previous_index = 0, name_size = name_.size();
template<const variable_info_type vit> for(size_t loop_index = 0; loop_index < name_size; loop_index++) {
void variable_info<vit>::calculate_value() switch(name_[loop_index]) {
{
// 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])
{
case '.': case '.':
case '[': case '[':
// '.', '[' mark the end of a string key. /* '.' and '[' mark the end of a string key.
// the result is oviously that '.' and '[' are * The result is obviously that '.' and '[' are
// treated equally so 'aaa.9].bbbb[zzz.uu.7]' * treated equally so 'aaa.9].bbbb[zzz.uu.7]'
// is interpreted as 'aaa[9].bbbb.zzz.uu[7]' * is interpreted as 'aaa[9].bbbb.zzz.uu[7]'
// use is_valid_variable function for stricter variablename checking. * Use is_valid_variable function for stricter variable name checking.
apply_visitor(get_variable_key_visitor<vit>(this->name_.substr(previous_index, loop_index-previous_index)), this->state_); */
apply_visitor<get_variable_key_visitor<V>>(
state_, name_.substr(previous_index, loop_index - previous_index));
previous_index = loop_index + 1; previous_index = loop_index + 1;
break; break;
case ']': case ']':
// ']' marks the end of an integer key. // ']' marks the end of an integer key.
apply_visitor(get_variable_index_visitor<vit>(parse_index(&this->name_[previous_index])), this->state_); 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 '.' // After ']' we always expect a '.' or the end of the string
// Ignore the next char which is a '.'
loop_index++; loop_index++;
if(loop_index < this->name_.length() && this->name_[loop_index] != '.') if(loop_index < name_.length() && name_[loop_index] != '.') {
{
throw invalid_variablename_exception(); throw invalid_variablename_exception();
} }
previous_index = loop_index + 1; previous_index = loop_index + 1;
break; break;
default: default:
break; break;
} }
} }
if(previous_index != this->name_.length() + 1)
{ if(previous_index != name_.length() + 1) {
// the string ended not with ']' // The string didn't end with ']'
// in this case we still didn't add the key behind the last '.' // 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_); apply_visitor<get_variable_key_visitor<V>>(state_, name_.substr(previous_index));
} }
} }
template<const variable_info_type vit> template<typename V>
bool variable_info<vit>::explicit_index() const bool variable_info<V>::explicit_index() const NOEXCEPT
{ {
throw_on_invalid(); 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> template<typename V>
typename maybe_const<vit, config::attribute_value>::type& variable_info<vit>::as_scalar() const maybe_const_t<config::attribute_value, V>& variable_info<V>::as_scalar() const
{ {
throw_on_invalid(); 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> template<typename V>
typename maybe_const<vit, config>::type& variable_info<vit>::as_container() const maybe_const_t<config, V>& variable_info<V>::as_container() const
{ {
throw_on_invalid(); 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> template<typename V>
typename maybe_const<vit, config::child_itors>::type variable_info<vit>::as_array() const maybe_const_t<config::child_itors, V> variable_info<V>::as_array() const
{ {
throw_on_invalid(); 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> template<typename V>
void variable_info<vit>::throw_on_invalid() const void variable_info<V>::throw_on_invalid() const
{ {
if(!this->valid_) if(!valid_) {
{
throw invalid_variablename_exception(); throw invalid_variablename_exception();
} }
} }
template<> template<typename V>
std::string variable_info<vit_const>::get_error_message() const std::string variable_info<V>::get_error_message() const
{ {
return "Cannot resolve variablename '" + this->name_ + "' for reading."; return V::error_message(name_);
} }
template<> template<typename V>
std::string variable_info<vit_create_if_not_existent>::get_error_message() const 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<> template<typename V>
std::string variable_info<vit_throw_if_not_existent>::get_error_message() const 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> template<typename V>
void non_const_variable_info<vit>::clear(bool only_tables) const void variable_info_mutable<V>::clear(bool only_tables) const
{ {
this->throw_on_invalid(); 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(); 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> template<typename V>
config::child_itors non_const_variable_info<vit>::insert_array(std::vector<config> childs) const config::child_itors variable_info_mutable<V>::insert_array(std::vector<config> children) const
{ {
this->throw_on_invalid(); 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> template<typename V>
config::child_itors non_const_variable_info<vit>::replace_array(std::vector<config> childs) const config::child_itors variable_info_mutable<V>::replace_array(std::vector<config> children) const
{ {
this->throw_on_invalid(); 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> template<typename V>
void non_const_variable_info<vit>::merge_array(std::vector<config> childs) const void variable_info_mutable<V>::merge_array(std::vector<config> children) const
{ {
this->throw_on_invalid(); 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> // Force compilation of the following template instantiations
bool variable_info<vit>::exists_as_attribute() const template class variable_info<const vi_policy_const>;
{ template class variable_info<vi_policy_create>;
this->throw_on_invalid(); template class variable_info<vi_policy_throw>;
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_);
}
///explicit instantiations template class variable_info_mutable<vi_policy_create>;
template class variable_info<vit_const>; template class variable_info_mutable<vi_policy_throw>;
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>;

View file

@ -16,98 +16,136 @@
#pragma once #pragma once
/** Information on a WML variable. */
#include <string>
#include "config.hpp" #include "config.hpp"
#include "variable_info_detail.hpp"
#include <string>
#include <type_traits>
class invalid_variablename_exception : public std::exception class invalid_variablename_exception : public std::exception
{ {
public: public:
invalid_variablename_exception() : std::exception() {} invalid_variablename_exception() : std::exception() {}
const char* what() const NOEXCEPT const char* what() const NOEXCEPT
{ {
return "invalid_variablename_exception"; 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 class variable_info
{ {
public: 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; std::string get_error_message() const;
/// Doesn't throw
bool explicit_index() const; bool explicit_index() const NOEXCEPT;
/// might throw invalid_variablename_exception
/** @throws invalid_variablename_exception */
bool exists_as_attribute() const; bool exists_as_attribute() const;
/// might throw invalid_variablename_exception
/** @throws invalid_variablename_exception */
bool exists_as_container() const; bool exists_as_container() const;
/** /**
might throw invalid_variablename_exception * If instantiated with vi_policy_const, the lifetime of the returned
NOTE: * const attribute_value reference might end with the lifetime of this object.
If vit == vit_const, then the lifime of the returned const attribute_value& might end with the lifetime of this object. *
*/ * @throws invalid_variablename_exception
typename variable_info_detail::maybe_const<vit, config::attribute_value>::type &as_scalar() const; */
/// might throw invalid_variablename_exception maybe_const_t<config::attribute_value, V>& as_scalar() const;
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, 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: protected:
std::string name_; std::string name_;
variable_info_detail::variable_info_state<vit> state_; variable_info_implementation::variable_info_state<V> state_;
void throw_on_invalid() const;
bool valid_; bool valid_;
void throw_on_invalid() const;
void calculate_value(); 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> * Additional functionality for a non-const variable_info.
class non_const_variable_info : public variable_info<vit>, variable_info_detail::enable_if_non_const<vit>::type * @todo: should these functions take a reference?
*/
template<typename V>
class variable_info_mutable : public variable_info<V>
{ {
public: public:
non_const_variable_info(const std::string& name, config& game_vars) : variable_info<vit>(name, game_vars) {} variable_info_mutable(const std::string& name, config& game_vars)
~non_const_variable_info() {} : 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. * @returns The new appended range.
/// might throw invalid_variablename_exception * @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; 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. * Read-only access.
*/ *
typedef non_const_variable_info<variable_info_detail::vit_create_if_not_existent> variable_access_create; * 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.
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'. using variable_access_const = variable_info<const variable_info_implementation::vi_policy_const>;
*/
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;

View file

@ -16,80 +16,142 @@
#pragma once #pragma once
#include <string>
#include "config.hpp" #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 { * The variable_info policy classes.
state_start = 0, // for internal use *
// only used at the 'starting_pos' of the variable_info::calculate_value algorithm * Each of these classes describes a different behavior for reading data from a variable
state_named, // the result of .someval this can eigher man an attribute value or an * and should implement two functions:
// child range *
state_indexed, // the result of .someval[index] this is never an attribute value, * - get_child_at Describes the desired behavior when reading variable info.
// this is always a single config. * - error_message Error message regarding policy behavior.
state_temporary, // the result of .length this value can never be written, it can only be read. */
}; /** Takes a const reference and is guaranteed to not change the config. */
class vi_policy_const
//Special case of std::enable_if {
template<const variable_info_type vit> public:
struct enable_if_non_const static const config& get_child_at(const config& cfg, const std::string& key, int index)
{ {
typedef enable_if_non_const<vit> type; assert(index >= 0);
}; // cfg.child_or_empty does not support index parameter
if(const config& child = cfg.child(key, index)) {
template<> return child;
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;
} }
// The meaning of the following 3 depends on 'type_', but usualy the case is: static const config empty_const_cfg;
// the current config is child_->child_at(key_, index_). return empty_const_cfg;
child_t* child_; }
std::string key_;
int index_;
// If we have a temporary value like .length static std::string error_message(const std::string& name)
// Then we store the result here. {
config::attribute_value temp_val_; return "Cannot resolve variable '" + name + "' for reading.";
}
};
// See the definition of 'variable_info_state_type' /** Creates a child table when resolving name if it doesn't exist yet. */
variable_info_state_type type_; 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