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:
parent
4f888e2073
commit
cdaa588eda
4 changed files with 849 additions and 698 deletions
|
@ -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>;
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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;
|
||||
|
|
496
src/variable_info_private.hpp
Normal file
496
src/variable_info_private.hpp
Normal 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
|
Loading…
Add table
Reference in a new issue