Merge pull request #231 from gfgtdf/game_variables

refactor Game variables
we try to resolve dependedcies game_data/game_varibles
we now use c++ const correctness to ensure not to write the game variables if we just want to read them
we improve the performance of wesnoth.set/get_variable, especialy for explicit indexes.
in order to do that we rewrite variable_info class.
This commit is contained in:
gfgtdf 2014-07-05 03:37:27 +02:00
commit fa28ffba0f
19 changed files with 1551 additions and 691 deletions

View file

@ -906,6 +906,7 @@ set(wesnoth-main_SRC
unit_types.cpp
utils/sha1.cpp
variable.cpp
variable_info.cpp
variant.cpp
whiteboard/action.cpp
whiteboard/attack.cpp

View file

@ -538,6 +538,7 @@ wesnoth_sources = Split("""
unit_types.cpp
utils/sha1.cpp
variable.cpp
variable_info.cpp
variant.cpp
whiteboard/action.cpp
whiteboard/attack.cpp

View file

@ -49,6 +49,7 @@
#include "unit.hpp"
#include "unit_map.hpp"
#include <boost/assign.hpp>
#include <boost/bind.hpp>
#include <boost/foreach.hpp>
@ -75,7 +76,6 @@ game_data::game_data()
, wml_menu_items_()
, rng_()
, variables_()
, temporaries_()
, phase_(INITIAL)
, can_end_turn_(true)
, scenario_()
@ -88,7 +88,6 @@ game_data::game_data(const config& level)
, wml_menu_items_()
, rng_(level)
, variables_(level.child_or_empty("variables"))
, temporaries_()
, phase_(INITIAL)
, can_end_turn_(level["can_end_turn"].to_bool(true))
, scenario_(level["id"])
@ -104,69 +103,73 @@ game_data::game_data(const game_data& data)
, wml_menu_items_(data.wml_menu_items_)
, rng_(data.rng_)
, variables_(data.variables_)
, temporaries_()
, phase_(data.phase_)
, can_end_turn_(data.can_end_turn_)
, scenario_(data.scenario_)
, next_scenario_(data.next_scenario_)
{}
//throws
config::attribute_value &game_data::get_variable(const std::string& key)
{
return variable_info(key, true, variable_info::TYPE_SCALAR).as_scalar();
return get_variable_access_write(key).as_scalar();
}
config::attribute_value game_data::get_variable_const(const std::string &key) const
{
variable_info to_get(key, false, variable_info::TYPE_SCALAR);
if (!to_get.is_valid)
try
{
config::attribute_value &to_return = temporaries_[key];
if (key.size() > 7 && key.substr(key.size() - 7) == ".length") {
// length is a special attribute, so guarantee its correctness
to_return = 0;
}
return to_return;
return get_variable_access_read(key).as_scalar();
}
catch(const invalid_variablename_exception&)
{
return config::attribute_value();
}
return to_get.as_scalar();
}
//throws
config& game_data::get_variable_cfg(const std::string& key)
{
return variable_info(key, true, variable_info::TYPE_CONTAINER).as_container();
return get_variable_access_write(key).as_container();
}
void game_data::set_variable(const std::string& key, const t_string& value)
{
get_variable(key) = value;
try
{
get_variable(key) = value;
}
catch(const invalid_variablename_exception&)
{
ERR_NG << "variable " << key << "cannot be set to " << value << std::endl;
}
}
//throws
config& game_data::add_variable_cfg(const std::string& key, const config& value)
{
variable_info to_add(key, true, variable_info::TYPE_ARRAY);
return to_add.vars->add_child(to_add.key, value);
std::vector<config> temp = boost::assign::list_of(value);
return *get_variable_access_write(key).append_array(temp).first;
}
void game_data::clear_variable_cfg(const std::string& varname)
{
variable_info to_clear(varname, false, variable_info::TYPE_CONTAINER);
if(!to_clear.is_valid) return;
if(to_clear.explicit_index) {
to_clear.vars->remove_child(to_clear.key, to_clear.index);
} else {
to_clear.vars->clear_children(to_clear.key);
try
{
get_variable_access_throw(varname).clear(true);
}
catch(const invalid_variablename_exception&)
{
//variable doesn't exist, nothing to delete
}
}
void game_data::clear_variable(const std::string& varname)
{
variable_info to_clear(varname, false);
if(!to_clear.is_valid) return;
if(to_clear.explicit_index) {
to_clear.vars->remove_child(to_clear.key, to_clear.index);
} else {
to_clear.vars->clear_children(to_clear.key);
to_clear.vars->remove_attribute(to_clear.key);
try
{
get_variable_access_throw(varname).clear(false);
}
catch(const invalid_variablename_exception&)
{
//variable doesn't exist, nothing to delete
}
}
@ -234,3 +237,33 @@ game_data* game_data::operator=(const game_data* info)
}
return this ;
}
namespace {
bool recursive_activation = false;
} // end anonymous namespace
void game_data::activate_scope_variable(std::string var_name) const
{
if(recursive_activation)
return;
const std::string::iterator itor = std::find(var_name.begin(),var_name.end(),'.');
if(itor != var_name.end()) {
var_name.erase(itor, var_name.end());
}
std::vector<scoped_wml_variable*>::const_reverse_iterator rit;
for(
rit = scoped_variables.rbegin();
rit != scoped_variables.rend();
++rit) {
if((**rit).name() == var_name) {
recursive_activation = true;
if(!(**rit).activated()) {
(**rit).activate();
}
recursive_activation = false;
break;
}
}
}

View file

@ -22,7 +22,7 @@
#include "game_events/wmi_container.hpp"
#include "map_location.hpp"
#include "simple_rng.hpp"
#include "variable_info.hpp"
#include <boost/shared_ptr.hpp>
class config_writer;
@ -58,6 +58,26 @@ public:
void set_variable(const std::string& varname, const t_string& value);
config& add_variable_cfg(const std::string& varname, const config& value=config());
void activate_scope_variable(std::string var_name) const;
// returns a variable_access that cannot be used to change the game variables
variable_access_const get_variable_access_read(const std::string& varname) const
{
assert(this != NULL);
activate_scope_variable(varname);
return variable_access_const(varname, variables_);
}
// returns a variable_access that cannot be used to change the game variables
variable_access_create get_variable_access_write(const std::string& varname)
{
assert(this != NULL);
activate_scope_variable(varname);
return variable_access_create(varname, variables_);
}
void clear_variable(const std::string& varname);
void clear_variable_cfg(const std::string& varname); // Clears only the config children
@ -103,11 +123,19 @@ public:
game_data* operator=(const game_data* info);
private:
///Used to delete variables.
variable_access_throw get_variable_access_throw(const std::string& varname)
{
assert(this != NULL);
activate_scope_variable(varname);
return variable_access_throw(varname, variables_);
}
game_events::wmi_container wml_menu_items_;
rand_rng::simple_rng rng_;
config variables_;
mutable config temporaries_; // lengths of arrays, etc.
friend struct variable_info;
PHASE phase_;
bool can_end_turn_;
std::string scenario_; /**< the scenario being played */

View file

@ -29,6 +29,7 @@
#include "../actions/move.hpp"
#include "../actions/vision.hpp"
#include "../ai/manager.hpp"
#include "../config_assign.hpp"
#include "../dialogs.hpp"
#include "../fake_unit_manager.hpp"
#include "../fake_unit_ptr.hpp"
@ -1839,298 +1840,287 @@ WML_HANDLER_FUNCTION(set_menu_item, /*event_info*/, cfg)
WML_HANDLER_FUNCTION(set_variable, /*event_info*/, cfg)
{
game_data *gameinfo = resources::gamedata;
const std::string name = cfg["name"];
if(name.empty()) {
ERR_NG << "trying to set a variable with an empty name:\n" << cfg.get_config().debug();
return;
}
config::attribute_value &var = gameinfo->get_variable(name);
config::attribute_value literal = cfg.get_config()["literal"]; // no $var substitution
if (!literal.blank()) {
var = literal;
}
config::attribute_value value = cfg["value"];
if (!value.blank()) {
var = value;
}
const std::string to_variable = cfg["to_variable"];
if(to_variable.empty() == false) {
var = gameinfo->get_variable(to_variable);
}
config::attribute_value add = cfg["add"];
if (!add.empty()) {
var = var.to_double() + add.to_double();
}
config::attribute_value sub = cfg["sub"];
if (!sub.empty()) {
var = var.to_double() - sub.to_double();
}
config::attribute_value multiply = cfg["multiply"];
if (!multiply.empty()) {
var = var.to_double() * multiply.to_double();
}
config::attribute_value divide = cfg["divide"];
if (!divide.empty()) {
if (divide.to_double() == 0) {
ERR_NG << "division by zero on variable " << name << std::endl;
try
{
if(name.empty()) {
ERR_NG << "trying to set a variable with an empty name:\n" << cfg.get_config().debug();
return;
}
var = var.to_double() / divide.to_double();
}
config::attribute_value &var = gameinfo->get_variable(name);
config::attribute_value modulo = cfg["modulo"];
if (!modulo.empty()) {
if (modulo.to_double() == 0) {
ERR_NG << "division by zero on variable " << name << std::endl;
return;
config::attribute_value literal = cfg.get_config()["literal"]; // no $var substitution
if (!literal.blank()) {
var = literal;
}
var = std::fmod(var.to_double(), modulo.to_double());
}
config::attribute_value round_val = cfg["round"];
if (!round_val.empty()) {
double value = var.to_double();
if (round_val == "ceil") {
var = int(std::ceil(value));
} else if (round_val == "floor") {
var = int(std::floor(value));
} else {
// We assume the value is an integer.
// Any non-numerical values will be interpreted as 0
// Which is probably what was intended anyway
int decimals = round_val.to_int();
value *= std::pow(10.0, decimals); //add $decimals zeroes
value = round_portable(value); // round() isn't implemented everywhere
value *= std::pow(10.0, -decimals); //and remove them
config::attribute_value value = cfg["value"];
if (!value.blank()) {
var = value;
}
}
config::attribute_value ipart = cfg["ipart"];
if (!ipart.empty()) {
double result;
std::modf(ipart.to_double(), &result);
var = int(result);
}
if(to_variable.empty() == false) {
var = gameinfo->get_variable_access_read(to_variable).as_scalar();
}
config::attribute_value fpart = cfg["fpart"];
if (!fpart.empty()) {
double ignore;
var = std::modf(fpart.to_double(), &ignore);
}
config::attribute_value add = cfg["add"];
if (!add.empty()) {
var = var.to_double() + add.to_double();
}
config::attribute_value string_length_target = cfg["string_length"];
if (!string_length_target.blank()) {
var = int(string_length_target.str().size());
}
config::attribute_value sub = cfg["sub"];
if (!sub.empty()) {
var = var.to_double() - sub.to_double();
}
// Note: maybe we add more options later, eg. strftime formatting.
// For now make the stamp mandatory.
const std::string time = cfg["time"];
if(time == "stamp") {
var = int(SDL_GetTicks());
}
config::attribute_value multiply = cfg["multiply"];
if (!multiply.empty()) {
var = var.to_double() * multiply.to_double();
}
// Random generation works as follows:
// rand=[comma delimited list]
// Each element in the list will be considered a separate choice,
// unless it contains "..". In this case, it must be a numerical
// range (i.e. -1..-10, 0..100, -10..10, etc).
const std::string rand = cfg["rand"];
// The new random generator, the logic is a copy paste of the old random.
if(rand.empty() == false) {
assert(gameinfo);
// A default value in case something goes really wrong.
var = "";
std::string word;
std::vector<std::string> words;
std::vector<std::pair<long,long> > ranges;
long num_choices = 0;
std::string::size_type pos = 0, pos2 = std::string::npos;
std::stringstream ss(std::stringstream::in|std::stringstream::out);
while (pos2 != rand.length()) {
pos = pos2+1;
pos2 = rand.find(",", pos);
if (pos2 == std::string::npos)
pos2 = rand.length();
word = rand.substr(pos, pos2-pos);
words.push_back(word);
std::string::size_type tmp = word.find("..");
if (tmp == std::string::npos) {
// Treat this element as a string
ranges.push_back(std::pair<long, long>(0,0));
num_choices += 1;
config::attribute_value divide = cfg["divide"];
if (!divide.empty()) {
if (divide.to_double() == 0) {
ERR_NG << "division by zero on variable " << name << std::endl;
return;
}
else {
// Treat as a numerical range
const std::string first = word.substr(0, tmp);
const std::string second = word.substr(tmp+2,
rand.length());
var = var.to_double() / divide.to_double();
}
long low, high;
ss << first + " " + second;
if ( !(ss >> low) || !(ss >> high) ) {
ERR_NG << "Malformed range: rand = \"" << rand << "\"" << std::endl;
// Treat this element as a string?
config::attribute_value modulo = cfg["modulo"];
if (!modulo.empty()) {
if (modulo.to_double() == 0) {
ERR_NG << "division by zero on variable " << name << std::endl;
return;
}
var = std::fmod(var.to_double(), modulo.to_double());
}
config::attribute_value round_val = cfg["round"];
if (!round_val.empty()) {
double value = var.to_double();
if (round_val == "ceil") {
var = int(std::ceil(value));
} else if (round_val == "floor") {
var = int(std::floor(value));
} else {
// We assume the value is an integer.
// Any non-numerical values will be interpreted as 0
// Which is probably what was intended anyway
int decimals = round_val.to_int();
value *= std::pow(10.0, decimals); //add $decimals zeroes
value = round_portable(value); // round() isn't implemented everywhere
value *= std::pow(10.0, -decimals); //and remove them
var = value;
}
}
config::attribute_value ipart = cfg["ipart"];
if (!ipart.empty()) {
double result;
std::modf(ipart.to_double(), &result);
var = int(result);
}
config::attribute_value fpart = cfg["fpart"];
if (!fpart.empty()) {
double ignore;
var = std::modf(fpart.to_double(), &ignore);
}
config::attribute_value string_length_target = cfg["string_length"];
if (!string_length_target.blank()) {
var = int(string_length_target.str().size());
}
// Note: maybe we add more options later, eg. strftime formatting.
// For now make the stamp mandatory.
const std::string time = cfg["time"];
if(time == "stamp") {
var = int(SDL_GetTicks());
}
// Random generation works as follows:
// rand=[comma delimited list]
// Each element in the list will be considered a separate choice,
// unless it contains "..". In this case, it must be a numerical
// range (i.e. -1..-10, 0..100, -10..10, etc).
const std::string rand = cfg["rand"];
// The new random generator, the logic is a copy paste of the old random.
if(rand.empty() == false) {
assert(gameinfo);
// A default value in case something goes really wrong.
var = "";
std::string word;
std::vector<std::string> words;
std::vector<std::pair<long,long> > ranges;
long num_choices = 0;
std::string::size_type pos = 0, pos2 = std::string::npos;
std::stringstream ss(std::stringstream::in|std::stringstream::out);
while (pos2 != rand.length()) {
pos = pos2+1;
pos2 = rand.find(",", pos);
if (pos2 == std::string::npos)
pos2 = rand.length();
word = rand.substr(pos, pos2-pos);
words.push_back(word);
std::string::size_type tmp = word.find("..");
if (tmp == std::string::npos) {
// Treat this element as a string
ranges.push_back(std::pair<long, long>(0,0));
num_choices += 1;
}
else {
if (low > high) {
std::swap(low, high);
}
ranges.push_back(std::pair<long, long>(low,high));
num_choices += (high - low) + 1;
// Treat as a numerical range
const std::string first = word.substr(0, tmp);
const std::string second = word.substr(tmp+2,
rand.length());
// Make 0..0 ranges work
if (high == 0 && low == 0) {
words.pop_back();
words.push_back("0");
long low, high;
ss << first + " " + second;
if ( !(ss >> low) || !(ss >> high) ) {
ERR_NG << "Malformed range: rand = \"" << rand << "\"" << std::endl;
// Treat this element as a string?
ranges.push_back(std::pair<long, long>(0,0));
num_choices += 1;
}
else {
if (low > high) {
std::swap(low, high);
}
ranges.push_back(std::pair<long, long>(low,high));
num_choices += (high - low) + 1;
// Make 0..0 ranges work
if (high == 0 && low == 0) {
words.pop_back();
words.push_back("0");
}
}
ss.clear();
}
ss.clear();
}
}
/*
* Choice gets a value in the range [0..32768).
* So need to add a second set of random values when a value
* outside the range is requested.
*/
if(num_choices > 0x3fffffff) {
WRN_NG << "Requested random number with an upper bound of "
/*
* Choice gets a value in the range [0..32768).
* So need to add a second set of random values when a value
* outside the range is requested.
*/
if(num_choices > 0x3fffffff) {
WRN_NG << "Requested random number with an upper bound of "
<< num_choices
<< " however the maximum number generated will be "
<< 0x3fffffff
<< ".\n";
}
long choice = random_new::generator->next_random();// gameinfo->rng().get_next_random();
if(num_choices >= 32768) {
choice <<= 15;
choice += random_new::generator->next_random();//gameinfo->rng().get_next_random();
}
choice %= num_choices;
long tmp = 0;
for(size_t i = 0; i < ranges.size(); ++i) {
tmp += (ranges[i].second - ranges[i].first) + 1;
if (tmp > choice) {
if (ranges[i].first == 0 && ranges[i].second == 0) {
var = words[i];
}
long choice = random_new::generator->next_random();// gameinfo->rng().get_next_random();
if(num_choices >= 32768) {
choice <<= 15;
choice += random_new::generator->next_random();//gameinfo->rng().get_next_random();
}
choice %= num_choices;
long tmp = 0;
for(size_t i = 0; i < ranges.size(); ++i) {
tmp += (ranges[i].second - ranges[i].first) + 1;
if (tmp > choice) {
if (ranges[i].first == 0 && ranges[i].second == 0) {
var = words[i];
}
else {
var = (ranges[i].second - (tmp - choice)) + 1;
}
break;
}
else {
var = (ranges[i].second - (tmp - choice)) + 1;
}
break;
}
}
const vconfig::child_list join_elements = cfg.get_children("join");
if(!join_elements.empty())
{
const vconfig & join_element = join_elements.front();
std::string array_name=join_element["variable"];
std::string separator=join_element["separator"];
std::string key_name=join_element["key"];
if(key_name.empty())
{
key_name="value";
}
bool remove_empty = join_element["remove_empty"].to_bool();
std::string joined_string;
variable_access_const vi = resources::gamedata->get_variable_access_read(array_name);
bool first = true;
BOOST_FOREACH(const config &cfg, vi.as_array())
{
std::string current_string = cfg[key_name];
if (remove_empty && current_string.empty()) continue;
if (first) first = false;
else joined_string += separator;
joined_string += current_string;
}
var = joined_string;
}
}
const vconfig::child_list join_elements = cfg.get_children("join");
if(!join_elements.empty())
catch(const invalid_variablename_exception&)
{
const vconfig & join_element = join_elements.front();
std::string array_name=join_element["variable"];
std::string separator=join_element["separator"];
std::string key_name=join_element["key"];
if(key_name.empty())
{
key_name="value";
}
bool remove_empty = join_element["remove_empty"].to_bool();
std::string joined_string;
variable_info vi(array_name, true, variable_info::TYPE_ARRAY);
bool first = true;
BOOST_FOREACH(const config &cfg, vi.as_array())
{
std::string current_string = cfg[key_name];
if (remove_empty && current_string.empty()) continue;
if (first) first = false;
else joined_string += separator;
joined_string += current_string;
}
var=joined_string;
ERR_NG << "Found invalid variablename in \n[set_variable]\n" << cfg.get_config().debug() << "[/set_variable]\n where name = " << name << " to_variable = " << to_variable << "\n";
}
}
WML_HANDLER_FUNCTION(set_variables, /*event_info*/, cfg)
{
const t_string& name = cfg["name"];
variable_info dest(name, true, variable_info::TYPE_CONTAINER);
variable_access_create dest = resources::gamedata->get_variable_access_write(name);
if(name.empty()) {
ERR_NG << "trying to set a variable with an empty name:\n" << cfg.get_config().debug();
return;
}
std::string mode = cfg["mode"]; // replace, append, merge, or insert
if(mode == "extend") {
mode = "append";
} else if(mode != "append" && mode != "merge") {
if(mode == "insert") {
size_t child_count = dest.vars->child_count(dest.key);
if(dest.index >= child_count) {
while(dest.index >= ++child_count) {
//inserting past the end requires empty data
dest.vars->append(config(dest.key));
}
//inserting at the end is handled by an append
mode = "append";
}
} else {
mode = "replace";
}
}
const vconfig::child_list values = cfg.get_children("value");
const vconfig::child_list literals = cfg.get_children("literal");
const vconfig::child_list split_elements = cfg.get_children("split");
config data;
std::vector<config> data;
if(cfg.has_attribute("to_variable"))
{
variable_info tovar(cfg["to_variable"], false, variable_info::TYPE_CONTAINER);
if(tovar.is_valid) {
if(tovar.explicit_index) {
data.add_child(dest.key, tovar.as_container());
} else {
variable_info::array_range range = tovar.as_array();
while(range.first != range.second)
{
data.add_child(dest.key, *range.first++);
}
try
{
variable_access_const tovar = resources::gamedata->get_variable_access_read(cfg["to_variable"]);
BOOST_FOREACH(const config& c, tovar.as_array())
{
data.push_back(c);
}
}
catch(const invalid_variablename_exception&)
{
ERR_NG << "Cannot do [set_variables] with invalid to_variable variable: " << cfg["to_variable"] << " with " << cfg.get_config().debug() << std::endl;
}
} else if(!values.empty()) {
for(vconfig::child_list::const_iterator i=values.begin(); i!=values.end(); ++i)
{
data.add_child(dest.key, (*i).get_parsed_config());
data.push_back((*i).get_parsed_config());
}
} else if(!literals.empty()) {
for(vconfig::child_list::const_iterator i=literals.begin(); i!=literals.end(); ++i)
{
data.add_child(dest.key, i->get_config());
data.push_back(i->get_config());
}
} else if(!split_elements.empty()) {
const vconfig & split_element = split_elements.front();
@ -2163,37 +2153,41 @@ WML_HANDLER_FUNCTION(set_variables, /*event_info*/, cfg)
for(std::vector<std::string>::iterator i=split_vector.begin(); i!=split_vector.end(); ++i)
{
data.add_child(dest.key)[key_name]=*i;
data.push_back(config_of(key_name, *i));
}
}
if(mode == "replace")
{
if(dest.explicit_index) {
dest.vars->remove_child(dest.key, dest.index);
} else {
dest.vars->clear_children(dest.key);
}
}
if(!data.empty())
try
{
const std::string& mode = cfg["mode"];
if(mode == "merge")
{
if(dest.explicit_index) {
// merging multiple children into a single explicit index
// requires that they first be merged with each other
data.merge_children(dest.key);
dest.as_container().merge_with(data.child(dest.key));
} else {
dest.vars->merge_with(data);
}
} else if(mode == "insert" || dest.explicit_index) {
BOOST_FOREACH(const config &child, data.child_range(dest.key))
if(dest.explicit_index() && data.size() > 1)
{
dest.vars->add_child_at(dest.key, child, dest.index++);
//merge children into one
config merged_children;
BOOST_FOREACH(const config &cfg, data) {
merged_children.append(cfg);
}
data = boost::assign::list_of(merged_children);
}
} else {
dest.vars->append(data);
dest.merge_array(data);
}
else if(mode == "insert")
{
dest.insert_array(data);
}
else if(mode == "append")
{
dest.append_array(data);
}
else /*default if(mode == "replace")*/
{
dest.replace_array(data);
}
}
catch(const invalid_variablename_exception&)
{
ERR_NG << "Cannot do [set_variables] with invalid destination variable: " << name << " with " << cfg.get_config().debug() << std::endl;
}
}
@ -2233,10 +2227,16 @@ WML_HANDLER_FUNCTION(store_relative_dir, /*event_info*/, cfg)
std::string variable = cfg["variable"];
map_location::RELATIVE_DIR_MODE mode = static_cast<map_location::RELATIVE_DIR_MODE> (cfg["mode"].to_int(0));
try
{
variable_access_create store = resources::gamedata->get_variable_access_write(variable);
variable_info store(variable, true, variable_info::TYPE_SCALAR );
store.as_scalar() = map_location::write_direction(src.get_relative_dir(dst,mode));
store.as_scalar() = map_location::write_direction(src.get_relative_dir(dst,mode));
}
catch(const invalid_variablename_exception&)
{
ERR_NG << "Cannot do [store_relative_dir] with invalid destination variable: " << variable << " with " << cfg.get_config().debug() << std::endl;
}
}
/// Store the rotation of one hex around another in a WML variable.
@ -2263,10 +2263,17 @@ WML_HANDLER_FUNCTION(store_rotate_map_location, /*event_info*/, cfg)
std::string variable = cfg["variable"];
int angle = cfg["angle"].to_int(1);
try
{
variable_access_create store = resources::gamedata->get_variable_access_write(variable);
variable_info store(variable, true, variable_info::TYPE_CONTAINER );
dst.rotate_right_around_center(src,angle).write(store.as_container());
dst.rotate_right_around_center(src,angle).write(store.as_container());
}
catch(const invalid_variablename_exception&)
{
ERR_NG << "Cannot do [store_rotate_map_location] with invalid destination variable: " << variable << " with " << cfg.get_config().debug() << std::endl;
}
}
@ -2284,10 +2291,15 @@ WML_HANDLER_FUNCTION(store_time_of_day, /*event_info*/, cfg)
if(variable.empty()) {
variable = "time_of_day";
}
variable_info store(variable, true, variable_info::TYPE_CONTAINER);
tod.write(store.as_container());
try
{
variable_access_create store = resources::gamedata->get_variable_access_write(variable);
tod.write(store.as_container());
}
catch(const invalid_variablename_exception&)
{
ERR_NG << "Found invalid variablename " << variable << " in [store_time_of_day] with " << cfg.get_config().debug() << "\n";
}
}
WML_HANDLER_FUNCTION(teleport, event_info, cfg)
@ -2452,12 +2464,20 @@ WML_HANDLER_FUNCTION(unit, /*event_info*/, cfg)
{
parsed_cfg.remove_attribute("to_variable");
unit new_unit(parsed_cfg, true);
config &var = resources::gamedata->get_variable_cfg(to_variable);
var.clear();
new_unit.write(var);
if (const config::attribute_value *v = parsed_cfg.get("x")) var["x"] = *v;
if (const config::attribute_value *v = parsed_cfg.get("y")) var["y"] = *v;
try
{
config &var = resources::gamedata->get_variable_cfg(to_variable);
var.clear();
new_unit.write(var);
if (const config::attribute_value *v = parsed_cfg.get("x")) var["x"] = *v;
if (const config::attribute_value *v = parsed_cfg.get("y")) var["y"] = *v;
}
catch(const invalid_variablename_exception&)
{
ERR_NG << "Cannot do [unit] with invalid to_variable: " << to_variable << " with " << cfg.get_config().debug() << std::endl;
}
return;
}
int side = parsed_cfg["side"].to_int(1);
@ -2487,9 +2507,9 @@ WML_HANDLER_FUNCTION(unit, /*event_info*/, cfg)
/// Unit serialization from variables
WML_HANDLER_FUNCTION(unstore_unit, /*event_info*/, cfg)
{
const config &var = resources::gamedata->get_variable_cfg(cfg["variable"]);
try {
const config &var = resources::gamedata->get_variable_cfg(cfg["variable"]);
config tmp_cfg(var);
const unit_ptr u = unit_ptr( new unit(tmp_cfg, false));
@ -2578,7 +2598,12 @@ WML_HANDLER_FUNCTION(unstore_unit, /*event_info*/, cfg)
(*resources::teams)[u->side() - 1].have_leader();
}
} catch (game::game_error &e) {
}
catch (const invalid_variablename_exception&)
{
ERR_NG << "invlid variable name in unstore_unit" << std::endl;
}
catch (game::game_error &e) {
ERR_NG << "could not de-serialize unit: '" << e.message << "'" << std::endl;
}
}

View file

@ -3091,8 +3091,16 @@ void console_handler::do_set_var() {
if(j != data.end()) {
const std::string name(data.begin(),j);
const std::string value(j+1,data.end());
resources::gamedata->set_variable(name,value);
} else {
try
{
resources::gamedata->set_variable(name,value);
}
catch(const invalid_variablename_exception&)
{
command_failed(_("Variable not found"));
}
}
else {
command_failed(_("Variable not found"));
}
}

View file

@ -31,6 +31,7 @@
//TODO: remove LOG_PERSIST, ERR_PERSIST from persist_context.hpp to .cpp files.
#define DBG_PERSIST LOG_STREAM(debug, log_persist)
#define ERR_PERSIST LOG_STREAM(err, log_persist)
struct persist_choice: mp_sync::user_choice {
const persist_context &ctx;
@ -62,17 +63,24 @@ static void get_global_variable(persist_context &ctx, const vconfig &pcfg)
int side = pcfg_side.str() == "global" ? resources::controller->current_side() : pcfg_side.to_int();
persist_choice choice(ctx,global,side);
config cfg = mp_sync::get_user_choice("global_variable",choice,side).child("variables");
if (cfg) {
size_t arrsize = cfg.child_count(global);
if (arrsize == 0) {
resources::gamedata->set_variable(local,cfg[global]);
try
{
if (cfg) {
size_t arrsize = cfg.child_count(global);
if (arrsize == 0) {
resources::gamedata->set_variable(local,cfg[global]);
} else {
resources::gamedata->clear_variable(local);
for (size_t i = 0; i < arrsize; i++)
resources::gamedata->add_variable_cfg(local,cfg.child(global,i));
}
} else {
resources::gamedata->clear_variable(local);
for (size_t i = 0; i < arrsize; i++)
resources::gamedata->add_variable_cfg(local,cfg.child(global,i));
resources::gamedata->set_variable(local,"");
}
} else {
resources::gamedata->set_variable(local,"");
}
catch(const invalid_variablename_exception&)
{
ERR_PERSIST << "cannot store global variable into invalid variablename " << local << std::endl;
}
}
@ -93,7 +101,14 @@ static void set_global_variable(persist_context &ctx, const vconfig &pcfg)
const config &vars = resources::gamedata->get_variables();
size_t arraylen = vars.child_count(local);
if (arraylen == 0) {
val = pack_scalar(global,resources::gamedata->get_variable(local));
try
{
val = pack_scalar(global,resources::gamedata->get_variable(local));
}
catch(const invalid_variablename_exception&)
{
val = config();
}
} else {
for (size_t i = 0; i < arraylen; i++)
val.add_child(global,vars.child(local,i));

View file

@ -994,20 +994,31 @@ static int intf_fire_event(lua_State *L)
static int intf_get_variable(lua_State *L)
{
char const *m = luaL_checkstring(L, 1);
variable_info v(m, false, variable_info::TYPE_SCALAR);
if (v.is_valid) {
luaW_pushscalar(L, v.as_scalar());
return 1;
} else {
variable_info w(m, false, variable_info::TYPE_CONTAINER);
if (w.is_valid) {
lua_newtable(L);
if (lua_toboolean(L, 2))
luaW_filltable(L, w.as_container());
variable_access_const v = resources::gamedata->get_variable_access_read(m);
try
{
if(v.exists_as_attribute())
{
luaW_pushscalar(L, v.as_scalar());
return 1;
}
else if(v.exists_as_container())
{
lua_newtable(L);
if (lua_toboolean(L, 2))
luaW_filltable(L, v.as_container());
return 1;
}
else
{
return 0;
}
}
catch (const invalid_variablename_exception&)
{
//TODO: pop table
return 0;
}
return 0;
}
/**
@ -1023,9 +1034,10 @@ static int intf_set_variable(lua_State *L)
resources::gamedata->clear_variable(m);
return 0;
}
variable_info v(m);
switch (lua_type(L, 2)) {
try
{
variable_access_create v = resources::gamedata->get_variable_access_write(m);
switch (lua_type(L, 2)) {
case LUA_TBOOLEAN:
v.as_scalar() = luaW_toboolean(L, 2);
break;
@ -1042,15 +1054,20 @@ static int intf_set_variable(lua_State *L)
}
// no break
case LUA_TTABLE:
{
config &cfg = v.as_container();
cfg.clear();
if (luaW_toconfig(L, 2, cfg))
break;
// no break
}
{
config &cfg = v.as_container();
cfg.clear();
if (luaW_toconfig(L, 2, cfg))
break;
// no break
}
default:
return luaL_typerror(L, 2, "WML table or scalar");
}
}
catch (const invalid_variablename_exception&)
{
ERR_LUA << "invlid variable name in wesnoth.set_veriable" << std::endl;
}
return 0;
}

View file

@ -300,12 +300,19 @@ SYNCED_COMMAND_HANDLER_FUNCTION(move, child, use_undo, show, error_handler)
return true;
}
SYNCED_COMMAND_HANDLER_FUNCTION(fire_event, child, /*use_undo*/, /*show*/, /*error_handler*/)
SYNCED_COMMAND_HANDLER_FUNCTION(fire_event, child, /*use_undo*/, /*show*/, error_handler)
{
//i don't know the reason for the following three lines.
//TODO: find out wheter we can delete them. I think this code was introduced in bbfdfcf9ed6ca44f01da32bf74c39d5fa9a75c37
BOOST_FOREACH(const config &v, child.child_range("set_variable")) {
resources::gamedata->set_variable(v["name"], v["value"]);
try
{
resources::gamedata->set_variable(v["name"], v["value"]);
}
catch(const invalid_variablename_exception&)
{
error_handler("invalid variable name", false);
}
}
bool undoable = true;

View file

@ -20,8 +20,10 @@
#include "display_context.hpp"
#include "filter_context.hpp"
#include "game_board.hpp"
#include "game_data.hpp"
#include "log.hpp"
#include "map.hpp"
#include "resources.hpp"
#include "side_filter.hpp"
#include "team.hpp"
#include "terrain_filter.hpp"
@ -132,13 +134,10 @@ bool terrain_filter::match_internal(const map_location& loc, const bool ignore_x
}
//allow filtering by searching a stored variable of locations
if(cfg_.has_attribute("find_in")) {
variable_info vi(cfg_["find_in"], false, variable_info::TYPE_CONTAINER);
if(!vi.is_valid) return false;
if(vi.explicit_index) {
if(map_location(vi.as_container(),NULL) != loc) {
return false;
}
} else {
try
{
variable_access_const vi = resources::gamedata->get_variable_access_read(cfg_["find_in"]);
bool found = false;
BOOST_FOREACH(const config &cfg, vi.as_array()) {
if (map_location(cfg, NULL) == loc) {
@ -148,6 +147,10 @@ bool terrain_filter::match_internal(const map_location& loc, const bool ignore_x
}
if (!found) return false;
}
catch(const invalid_variablename_exception&)
{
return false;
}
}
}
@ -417,18 +420,20 @@ void terrain_filter::get_locations(std::set<map_location>& locs, bool with_borde
&& !cfg_.has_attribute("area") ) {
//use content of find_in as starting set
variable_info vi(cfg_["find_in"], false, variable_info::TYPE_CONTAINER);
if(vi.is_valid) {
if(vi.explicit_index) {
map_location test_loc(vi.as_container(),NULL);
try
{
variable_access_const vi = resources::gamedata->get_variable_access_read(cfg_["find_in"]);
BOOST_FOREACH(const config& cfg, vi.as_array())
{
map_location test_loc(cfg, NULL);
match_set.insert(test_loc);
} else {
BOOST_FOREACH(const config &cfg, vi.as_array()) {
map_location test_loc(cfg, NULL);
match_set.insert(test_loc);
}
}
}
catch(const invalid_variablename_exception&)
{
//Do nothing
}
} else
// Only area provided
@ -450,20 +455,12 @@ void terrain_filter::get_locations(std::set<map_location>& locs, bool with_borde
match_set.insert(xy_vector.begin(), xy_vector.end());
// remove any locations not found in the specified variable
variable_info vi(cfg_["find_in"], false, variable_info::TYPE_CONTAINER);
if(!vi.is_valid) {
match_set.clear();
} else if(vi.explicit_index) {
map_location test_loc(vi.as_container(),NULL);
if(match_set.count(test_loc)) {
match_set.clear();
match_set.insert(test_loc);
} else {
match_set.clear();
}
} else {
try
{
std::set<map_location> findin_locs;
BOOST_FOREACH(const config &cfg, vi.as_array()) {
variable_access_const vi = resources::gamedata->get_variable_access_read(cfg_["find_in"]);
BOOST_FOREACH(const config& cfg, vi.as_array())
{
map_location test_loc(cfg, NULL);
if (match_set.count(test_loc)) {
findin_locs.insert(test_loc);
@ -471,6 +468,10 @@ void terrain_filter::get_locations(std::set<map_location>& locs, bool with_borde
}
match_set.swap(findin_locs);
}
catch(const invalid_variablename_exception&)
{
match_set.clear();
}
} else
// xy + area
@ -496,20 +497,21 @@ void terrain_filter::get_locations(std::set<map_location>& locs, bool with_borde
const std::set<map_location>& area = fc_->get_tod_man().get_area_by_id(cfg_["area"]);
//use content of find_in as starting set
variable_info vi(cfg_["find_in"], false, variable_info::TYPE_CONTAINER);
if(!vi.is_valid) {
match_set.clear(); //TODO not needed
} else if(vi.explicit_index) {
map_location test_loc(vi.as_container(),NULL);
if (area.count(test_loc) != 0)
match_set.insert(test_loc);
} else {
BOOST_FOREACH(const config &cfg, vi.as_array()) {
try
{
variable_access_const vi = resources::gamedata->get_variable_access_read(cfg_["find_in"]);
BOOST_FOREACH(const config& cfg, vi.as_array())
{
map_location test_loc(cfg, NULL);
if (area.count(test_loc) != 0)
match_set.insert(test_loc);
}
}
catch(const invalid_variablename_exception&)
{
match_set.clear();
}
} else
// area + find_in + xy
@ -524,20 +526,20 @@ void terrain_filter::get_locations(std::set<map_location>& locs, bool with_borde
const std::set<map_location>& area = fc_->get_tod_man().get_area_by_id(cfg_["area"]);
//use content of find_in as starting set
variable_info vi(cfg_["find_in"], false, variable_info::TYPE_CONTAINER);
if(vi.is_valid) {
if(vi.explicit_index) {
map_location test_loc(vi.as_container(),NULL);
try
{
variable_access_const vi = resources::gamedata->get_variable_access_read(cfg_["find_in"]);
BOOST_FOREACH(const config &cfg, vi.as_array()) {
map_location test_loc(cfg, NULL);
if (area.count(test_loc) != 0 && xy_set.count(test_loc) != 0)
match_set.insert(test_loc);
} else {
BOOST_FOREACH(const config &cfg, vi.as_array()) {
map_location test_loc(cfg, NULL);
if (area.count(test_loc) != 0 && xy_set.count(test_loc) != 0)
match_set.insert(test_loc);
}
}
}
catch(const invalid_variablename_exception&)
{
//Do nothing
}
}
//handle location filter

View file

@ -18,6 +18,7 @@
#include <cmath>
#include "config.hpp"
#include "variable_info.hpp"
BOOST_AUTO_TEST_SUITE ( test_config )
@ -172,4 +173,58 @@ BOOST_AUTO_TEST_CASE ( test_config_attribute_value )
BOOST_CHECK_EQUAL(cc["x"], c["x"]);
}
BOOST_AUTO_TEST_CASE ( test_variable_info )
{
config c;
{
variable_access_const access("", c);
// We dotn allow empty keys
BOOST_CHECK_THROW (access.as_scalar(), invalid_variablename_exception);
}
{
variable_access_const access("some_non_existent.", c);
// We dotn allow empty keys
BOOST_CHECK_THROW (access.as_scalar(), invalid_variablename_exception);
}
{
variable_access_const access("some_non_existent[0]value", c);
// We expect '.' after ']'
BOOST_CHECK_THROW (access.as_scalar(), invalid_variablename_exception);
}
{
variable_access_const access("some_non_existent", c);
// we return empty be default
BOOST_CHECK (!access.exists_as_container());
BOOST_CHECK_EQUAL (access.as_container(), config());
BOOST_CHECK (!access.exists_as_attribute());
BOOST_CHECK_EQUAL (access.as_scalar(), config::attribute_value());
}
{
variable_access_const access("a.b[0].c[1].d.e.f[2]", c);
// we return empty be default
BOOST_CHECK (!access.exists_as_container());
BOOST_CHECK_EQUAL (access.as_container(), config());
// Explicit indexes can never be an attribute
BOOST_CHECK_THROW (access.as_scalar(), invalid_variablename_exception);
}
BOOST_CHECK (c.empty());
{
config c2;
variable_access_create access("a.b[0].c[1].d.e.f[2].g", c2);
access.as_scalar() = 84;
BOOST_CHECK_EQUAL (variable_access_const("a.length", c2).as_scalar(), 1);
BOOST_CHECK_EQUAL (variable_access_const("a.b.length", c2).as_scalar(), 1);
BOOST_CHECK_EQUAL (variable_access_const("a.b.c.length", c2).as_scalar(), 2);
BOOST_CHECK_EQUAL (variable_access_const("a.b.c[1].d.e.f.length", c2).as_scalar(), 3);
// we setted g as a scalar
BOOST_CHECK_EQUAL (variable_access_const("a.b.c[1].d.e.f[2].g.length", c2).as_scalar(), 0);
BOOST_CHECK_EQUAL (variable_access_const("a.b.c[1].d.e.f[2].g", c2).as_scalar(), 84);
}
{
config c2;
variable_access_throw access("a.b[9].c", c2);
BOOST_CHECK_THROW(access.as_scalar(), invalid_variablename_exception);
}
}
BOOST_AUTO_TEST_SUITE_END()

View file

@ -22,6 +22,7 @@
#include "formula_string_utils.hpp" // for vgettext
#include "game_board.hpp" // for game_board
#include "game_data.hpp"
#include "game_config.hpp" // for add_color_info, etc
#include "game_errors.hpp" // for game_error
#include "game_events/handlers.hpp" // for add_events

View file

@ -18,6 +18,7 @@
#include "config.hpp"
#include "display_context.hpp"
#include "filter_context.hpp"
#include "game_data.hpp"
#include "make_enum.hpp"
#include "map_location.hpp"
#include "resources.hpp" //Needed for lua kernel pointer
@ -545,17 +546,23 @@ bool basic_unit_filter_impl::internal_matches_filter(const unit & u, const map_l
if (!cfg_find_in_.blank()) {
// Allow filtering by searching a stored variable of units
variable_info vi(cfg_find_in_, false, variable_info::TYPE_CONTAINER);
if(!vi.is_valid) return false;
if(vi.explicit_index) {
config::const_child_iterator i = vi.vars->child_range(vi.key).first;
std::advance(i, vi.index);
if ((*i)["id"] != u.id()) {
try
{
variable_access_const vi = resources::gamedata->get_variable_access_read(cfg_find_in_);
bool found_id = false;
BOOST_FOREACH(const config& c, vi.as_array())
{
if(c["id"] == u.id())
found_id = true;
}
if(!found_id)
{
return false;
}
} else {
if (!vi.vars->find_child(vi.key, "id", u.id()))
return false;
}
catch(const invalid_variablename_exception&)
{
return false;
}
}
if (!cfg_formula_.blank()) {

View file

@ -39,16 +39,6 @@ static lg::log_domain log_engine("engine");
#define WRN_NG LOG_STREAM(warn, log_engine)
#define ERR_NG LOG_STREAM(err, log_engine)
namespace
{
/**
* @todo FIXME: the variable repository should be
* a class of variable.hpp, and not the game_state.
*/
#define repos (resources::gamedata)
}
vconfig::vconfig() :
cache_(), cfg_(NULL)
{
@ -138,13 +128,11 @@ config vconfig::get_parsed_config() const
throw recursion_error("vconfig::get_parsed_config() infinite recursion detected, aborting");
}
try {
variable_info vinfo(vname, false, variable_info::TYPE_CONTAINER);
if(!vinfo.is_valid) {
res.add_child(name); //add empty tag
} else if(vinfo.explicit_index) {
variable_access_const vinfo = resources::gamedata->get_variable_access_read(vname);
if(vinfo.explicit_index()) {
res.add_child(name, vconfig(vinfo.as_container()).get_parsed_config());
} else {
variable_info::array_range range = vinfo.as_array();
config::const_child_itors range = vinfo.as_array();
if(range.first == range.second) {
res.add_child(name); //add empty tag
}
@ -152,8 +140,12 @@ config vconfig::get_parsed_config() const
res.add_child(name, vconfig(*range.first++).get_parsed_config());
}
}
vconfig_recursion.erase(vname);
} catch(recursion_error &err) {
}
catch(const invalid_variablename_exception&)
{
res.add_child(name);
}
catch(recursion_error &err) {
vconfig_recursion.erase(vname);
WRN_NG << err.message << std::endl;
if(vconfig_recursion.empty()) {
@ -163,6 +155,7 @@ config vconfig::get_parsed_config() const
throw;
}
}
vconfig_recursion.erase(vname);
} else {
res.add_child(child.key, vconfig(child.cfg).get_parsed_config());
}
@ -180,22 +173,28 @@ vconfig::child_list vconfig::get_children(const std::string& key) const
res.push_back(vconfig(child.cfg, cache_));
} else if (child.key == "insert_tag") {
vconfig insert_cfg(child.cfg);
if(insert_cfg["name"] == key) {
variable_info vinfo(insert_cfg["variable"], false, variable_info::TYPE_CONTAINER);
if(!vinfo.is_valid) {
//push back an empty tag
if(insert_cfg["name"] == key)
{
try
{
variable_access_const vinfo = resources::gamedata->get_variable_access_read(insert_cfg["variable"]);
if(vinfo.explicit_index()) {
res.push_back(vconfig(vinfo.as_container(), true));
} else {
config::const_child_itors range = vinfo.as_array();
if(range.first == range.second) {
//push back an empty tag
res.push_back(empty_vconfig());
}
while(range.first != range.second)
{
res.push_back(vconfig(*range.first++, true));
}
}
}
catch(const invalid_variablename_exception&)
{
res.push_back(empty_vconfig());
} else if(vinfo.explicit_index) {
res.push_back(vconfig(vinfo.as_container(), true));
} else {
variable_info::array_range range = vinfo.as_array();
if(range.first == range.second) {
//push back an empty tag
res.push_back(empty_vconfig());
}
while(range.first != range.second) {
res.push_back(vconfig(*range.first++, true));
}
}
}
}
@ -216,12 +215,17 @@ vconfig vconfig::child(const std::string& key) const
BOOST_FOREACH(const config &ins, cfg_->child_range("insert_tag"))
{
vconfig insert_cfg(ins);
if(insert_cfg["name"] == key) {
variable_info vinfo(insert_cfg["variable"], false, variable_info::TYPE_CONTAINER);
if(!vinfo.is_valid) {
if(insert_cfg["name"] == key)
{
try
{
variable_access_const vinfo = resources::gamedata->get_variable_access_read(insert_cfg["variable"]);
return vconfig(vinfo.as_container(), true);
}
catch(const invalid_variablename_exception&)
{
return empty_vconfig();
}
return vconfig(vinfo.as_container(), true);
}
}
return unconstructed_vconfig();
@ -266,7 +270,7 @@ namespace {
config::attribute_value vconfig::expand(const std::string &key) const
{
config::attribute_value val = (*cfg_)[key];
if (repos)
if (resources::gamedata)
val.apply_visitor(vconfig_expand_visitor(val));
return val;
}
@ -285,13 +289,19 @@ vconfig::all_children_iterator& vconfig::all_children_iterator::operator++()
{
if (inner_index_ >= 0 && i_->key == "insert_tag")
{
variable_info vinfo(vconfig(i_->cfg)["variable"], false, variable_info::TYPE_CONTAINER);
if(vinfo.is_valid && !vinfo.explicit_index) {
variable_info::array_range range = vinfo.as_array();
if (++inner_index_ < std::distance(range.first, range.second)) {
return *this;
try
{
variable_access_const vinfo = resources::gamedata->get_variable_access_read(vconfig(i_->cfg)["variable"]);
if(!vinfo.explicit_index()) {
config::const_child_itors range = vinfo.as_array();
if (++inner_index_ < std::distance(range.first, range.second)) {
return *this;
}
inner_index_ = 0;
}
inner_index_ = 0;
}
catch(const invalid_variablename_exception&)
{
}
}
++i_;
@ -330,15 +340,21 @@ vconfig vconfig::all_children_iterator::get_child() const
{
if (inner_index_ >= 0 && i_->key == "insert_tag")
{
variable_info vinfo(vconfig(i_->cfg)["variable"], false, variable_info::TYPE_CONTAINER);
if(!vinfo.is_valid) {
return empty_vconfig();
} else if(inner_index_ == 0) {
return vconfig(vinfo.as_container(), true);
try
{
variable_access_const vinfo = resources::gamedata->get_variable_access_read(vconfig(i_->cfg)["variable"]);
if(inner_index_ == 0) {
return vconfig(vinfo.as_container(), true);
} else {
config::const_child_itors r = vinfo.as_array();
std::advance(r.first, inner_index_);
return vconfig(*r.first, true);
}
}
catch(const invalid_variablename_exception&)
{
return empty_vconfig();
}
variable_info::array_range r = vinfo.as_array();
std::advance(r.first, inner_index_);
return vconfig(*r.first, true);
}
return vconfig(i_->cfg, cache_);
}
@ -369,22 +385,38 @@ scoped_wml_variable::scoped_wml_variable(const std::string& var_name) :
config &scoped_wml_variable::store(const config &var_value)
{
BOOST_FOREACH(const config &i, resources::gamedata->get_variables().child_range(var_name_)) {
previous_val_.add_child(var_name_, i);
try
{
BOOST_FOREACH(const config &i, resources::gamedata->get_variables().child_range(var_name_)) {
previous_val_.add_child(var_name_, i);
}
resources::gamedata->clear_variable_cfg(var_name_);
config &res = resources::gamedata->add_variable_cfg(var_name_, var_value);
LOG_NG << "scoped_wml_variable: var_name \"" << var_name_ << "\" has been auto-stored.\n";
activated_ = true;
return res;
}
resources::gamedata->clear_variable_cfg(var_name_);
config &res = resources::gamedata->add_variable_cfg(var_name_, var_value);
LOG_NG << "scoped_wml_variable: var_name \"" << var_name_ << "\" has been auto-stored.\n";
activated_ = true;
return res;
catch(const invalid_variablename_exception&)
{
assert(false && "invalid variable name of autostored varaible");
throw "assertion ignored";
}
}
scoped_wml_variable::~scoped_wml_variable()
{
if(activated_) {
resources::gamedata->clear_variable_cfg(var_name_);
BOOST_FOREACH(const config &i, previous_val_.child_range(var_name_)) {
resources::gamedata->add_variable_cfg(var_name_, i);
BOOST_FOREACH(const config &i, previous_val_.child_range(var_name_))
{
try
{
resources::gamedata->add_variable_cfg(var_name_, i);
}
catch(const invalid_variablename_exception&)
{
}
}
LOG_NG << "scoped_wml_variable: var_name \"" << var_name_ << "\" has been reverted.\n";
}
@ -442,202 +474,4 @@ void scoped_recall_unit::activate()
}
}
namespace {
bool recursive_activation = false;
/** Turns on any auto-stored variables */
void activate_scope_variable(std::string var_name)
{
if(recursive_activation)
return;
const std::string::iterator itor = std::find(var_name.begin(),var_name.end(),'.');
if(itor != var_name.end()) {
var_name.erase(itor, var_name.end());
}
std::vector<scoped_wml_variable*>::reverse_iterator rit;
for(rit = resources::gamedata->scoped_variables.rbegin(); rit != resources::gamedata->scoped_variables.rend(); ++rit) {
if((**rit).name() == var_name) {
recursive_activation = true;
if(!(**rit).activated()) {
(**rit).activate();
}
recursive_activation = false;
break;
}
}
}
} // end anonymous namespace
variable_info::variable_info(const std::string& varname,
bool force_valid, TYPE validation_type) :
vartype(validation_type),
is_valid(false),
key(),
explicit_index(false),
index(0),
vars(NULL)
{
assert(repos != NULL);
activate_scope_variable(varname);
vars = &resources::gamedata->variables_;
key = varname;
std::string::const_iterator itor = std::find(key.begin(),key.end(),'.');
int dot_index = key.find('.');
bool force_length = false;
// example varname = "unit_store.modifications.trait[0]"
while(itor != key.end()) { // subvar access
std::string element=key.substr(0,dot_index);
key = key.substr(dot_index+1);
size_t inner_index = 0;
const std::string::iterator index_start = std::find(element.begin(),element.end(),'[');
const bool inner_explicit_index = index_start != element.end();
if(inner_explicit_index) {
const std::string::iterator index_end = std::find(index_start,element.end(),']');
const std::string index_str(index_start+1,index_end);
inner_index = static_cast<size_t>(lexical_cast_default<int>(index_str));
if(inner_index > game_config::max_loop) {
ERR_NG << "variable_info: index greater than " << game_config::max_loop
<< ", truncated\n";
inner_index = game_config::max_loop;
}
element = std::string(element.begin(),index_start);
}
size_t size = vars->child_count(element);
if(size <= inner_index) {
if(force_valid) {
// Add elements to the array until the requested size is attained
if(inner_explicit_index || key != "length") {
for(; size <= inner_index; ++size) {
vars->add_child(element);
}
}
} else if(inner_explicit_index) {
WRN_NG << "variable_info: invalid WML array index, "
<< varname << std::endl;
return;
} else if(varname.length() >= 7 && varname.substr(varname.length()-7) == ".length") {
// require '.' to avoid matching suffixes -> requires varname over key to always find length
// return length 0 for non-existent WML array (handled below)
force_length = true;
} else {
WRN_NG << "variable_info: retrieving member of non-existent WML container, "
<< varname << std::endl;
return;
}
}
if((!inner_explicit_index && key == "length") || force_length) {
switch(vartype) {
case variable_info::TYPE_ARRAY:
case variable_info::TYPE_CONTAINER:
WRN_NG << "variable_info: using reserved WML variable as wrong type, "
<< varname << std::endl;
is_valid = force_valid || resources::gamedata->temporaries_.child(varname);
break;
case variable_info::TYPE_SCALAR:
default:
// Store the length of the array as a temporary variable
resources::gamedata->temporaries_[varname] = int(size);
is_valid = true;
break;
}
key = varname;
vars = &resources::gamedata->temporaries_;
return;
}
vars = &vars->child(element, inner_index);
itor = std::find(key.begin(),key.end(),'.');
dot_index = key.find('.');
} // end subvar access
const std::string::iterator index_start = std::find(key.begin(),key.end(),'[');
explicit_index = index_start != key.end();
if(explicit_index) {
const std::string::iterator index_end = std::find(index_start,key.end(),']');
const std::string index_str(index_start+1,index_end);
index = static_cast<size_t>(lexical_cast_default<int>(index_str));
if(index > game_config::max_loop) {
ERR_NG << "variable_info: index greater than " << game_config::max_loop
<< ", truncated\n";
index = game_config::max_loop;
}
key = std::string(key.begin(),index_start);
size_t size = vars->child_count(key);
if(size <= index) {
if(!force_valid) {
WRN_NG << "variable_info: invalid WML array index, " << varname << std::endl;
return;
}
for(; size <= index; ++size) {
vars->add_child(key);
}
}
switch(vartype) {
case variable_info::TYPE_ARRAY:
vars = &vars->child(key, index);
key = "__array";
is_valid = force_valid || vars->child(key);
break;
case variable_info::TYPE_SCALAR:
vars = &vars->child(key, index);
key = "__value";
is_valid = force_valid || vars->has_attribute(key);
break;
case variable_info::TYPE_CONTAINER:
case variable_info::TYPE_UNSPECIFIED:
default:
is_valid = true;
return;
}
if (force_valid) {
WRN_NG << "variable_info: using explicitly indexed "
"container as wrong WML type, " << varname << '\n';
}
explicit_index = false;
index = 0;
} else {
// Final variable is not an explicit index [...]
switch(vartype) {
case variable_info::TYPE_ARRAY:
case variable_info::TYPE_CONTAINER:
is_valid = force_valid || vars->child(key);
break;
case variable_info::TYPE_SCALAR:
is_valid = force_valid || vars->has_attribute(key);
break;
case variable_info::TYPE_UNSPECIFIED:
default:
is_valid = true;
break;
}
}
}
config::attribute_value &variable_info::as_scalar()
{
assert(is_valid);
return (*vars)[key];
}
config& variable_info::as_container() {
assert(is_valid);
if(explicit_index) {
// Empty data for explicit index was already created if it was needed
return vars->child(key, index);
}
if (config &temp = vars->child(key)) {
// The container exists, index not specified, return index 0
return temp;
}
// Add empty data for the new variable, since it does not exist yet
return vars->add_child(key);
}
variable_info::array_range variable_info::as_array() {
assert(is_valid);
return vars->child_range(key);
}

View file

@ -131,6 +131,17 @@ public:
private:
Itor i_;
/*
if wa have game variables
[a] b = 1 [/a]
[a] b = 4 [/a]
[a] b = 6 [/a]
we want to expand [insert_tag] variable = a name = "u" [/insert_tag] to
[u] b = 1 [/u]
[u] b = 4 [/u]
[u] b = 6 [/u]
in this case inner_index_ points to the index in 'a' we are currently processing.
*/
int inner_index_;
boost::shared_ptr<config> cache_;
};
@ -209,37 +220,4 @@ private:
unsigned int recall_index_;
};
/** Information on a WML variable. */
struct variable_info
{
typedef config::child_itors array_range;
/**
* TYPE: the correct variable type should be decided by the user of the info structure
* Note: an Array can also be considered a Container, since index 0 will be used by default
*/
enum TYPE { TYPE_SCALAR, //a Scalar variable resolves to a t_string attribute of *vars
TYPE_ARRAY, //an Array variable is a series of Containers
TYPE_CONTAINER, //a Container is a specific index of an Array (contains Scalars)
TYPE_UNSPECIFIED };
variable_info(const std::string& varname, bool force_valid=true,
TYPE validation_type=TYPE_UNSPECIFIED);
TYPE vartype; //default is TYPE_UNSPECIFIED
bool is_valid;
std::string key; //the name of the internal attribute or child
bool explicit_index; //true if query ended in [...] specifier
size_t index; //the index of the child
config *vars; //the containing node in game_data s variables
/**
* Results: after deciding the desired type, these methods can retrieve the result
* Note: first you should force_valid or check is_valid, otherwise these may fail
*/
config::attribute_value &as_scalar();
config& as_container();
array_range as_array(); //range may be empty
};
#endif

640
src/variable_info.cpp Normal file
View file

@ -0,0 +1,640 @@
/*
Copyright (C) 2003 by David White <dave@whitevine.net>
Copyright (C) 2005 - 2014 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.
*/
/**
* @file
* Manage WML-variables.
*/
#include "variable_info.hpp"
#include "game_config.hpp"
using namespace variable_info_3_detail;
/// general helpers
namespace
{
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();
}
}
/// Checks if name is a valid key for an attribute_value/child
void check_valid_name(const std::string& name)
{
if(name.empty())
{
throw invalid_variablename_exception();
}
}
template<const variable_info_3_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);
check_valid_name(key);
// 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;
template<>
const config& get_child_at<vit_const>(const config& cfg, const std::string& key, int index)
{
assert(index >= 0);
check_valid_name(key);
//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);
check_valid_name(key);
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 TVisitor
/// TVisitor shoudl 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);
default:
throw std::exception();
}
};
template <const variable_info_3_type vit, typename TResult>
class variable_info_visitor
{
public:
typedef TResult result_type;
typedef variable_info_3_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_3_type vit, typename TResult>
class variable_info_visitor_const
{
public:
typedef TResult result_type;
typedef const variable_info_3_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.
/// 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);
if (*endptr != ']') {
res = 0;//default
}
if(res > int(game_config::max_loop))
{
throw invalid_variablename_exception();
}
return res;
}
/// Adds a '.<key>' to the current variable
template<const variable_info_3_type vit>
class get_variable_key_visitor
: public variable_info_visitor<vit, void>
{
public:
get_variable_key_visitor(const std::string& key) : key_(key) {}
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 dont 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_3_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
namespace
{
///tries to convert it to an (maybe const) attribute value
template<const variable_info_3_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
{
check_valid_name(state.key_);
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(typename 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(typename as_skalar_visitor::param_type) const
{
throw invalid_variablename_exception();
}
template<>
config::attribute_value & as_skalar_visitor<vit_throw_if_not_existent>::from_temporary(typename 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'
/// Note: Currently getting the 'from_start' case here is impossible, becasue we always apply at least one string key.
template<const variable_info_3_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_);
}
};
}
/// range_based operations
namespace {
template<const variable_info_3_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
{
//Ensure we have a config at the given explicit position.
get_child_at<vit>(*state.child_, state.key_, state.index_);
return handler_(*state.child_, state.key_, state.index_, state.index_ + 1);
}
private:
const THandler& handler_;
};
template<const variable_info_3_type vit>
class variable_as_array_h
{
public:
typedef typename maybe_const<vit, config::child_itors>::type result_type;
result_type operator()(typename maybe_const<vit, config>::type& child, const std::string& key, int startindex, int endindex) const
{
result_type r = child.child_range(key);
std::advance(r.first, startindex);
r.second = r.first;
std::advance(r.second, endindex - startindex);
return r;
}
};
/**
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
{
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]);
}
/// variable_as_array_h only uses the template argument (vit_throw_if_not_existent here)
/// to determine constness which is always false here
return variable_as_array_h<vit_throw_if_not_existent>()(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_3_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_3_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_3_type vit>
variable_info_3<vit>::variable_info_3(const std::string& varname, t_config& vars)
: name_(varname)
, state_(vars)
, valid_(true)
{
try
{
this->calculate_value();
}
catch(const invalid_variablename_exception&)
{
this->valid_ = false;
}
}
template<const variable_info_3_type vit>
variable_info_3<vit>::~variable_info_3()
{
}
template<const variable_info_3_type vit>
void variable_info_3<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])
{
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_);
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 '.'
loop_index++;
if(loop_index < this->name_.length() && this->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_);
}
}
template<const variable_info_3_type vit>
bool variable_info_3<vit>::explicit_index() const
{
throw_on_invalid();
return this->state_.type_ == state_start || this->state_.type_ == state_indexed;
}
template<const variable_info_3_type vit>
typename maybe_const<vit, config::attribute_value>::type& variable_info_3<vit>::as_scalar() const
{
throw_on_invalid();
return apply_visitor(as_skalar_visitor<vit>(), this->state_);
}
template<const variable_info_3_type vit>
typename maybe_const<vit, config>::type& variable_info_3<vit>::as_container() const
{
throw_on_invalid();
return apply_visitor(as_container_visitor<vit>(), this->state_);
}
template<const variable_info_3_type vit>
typename maybe_const<vit, config::child_itors>::type variable_info_3<vit>::as_array() const
{
throw_on_invalid();
return apply_visitor(as_range_visitor_base<vit,variable_as_array_h<vit> >(variable_as_array_h<vit>()), this->state_);
}
template<const variable_info_3_type vit>
void variable_info_3<vit>::throw_on_invalid() const
{
if(!this->valid_)
{
throw invalid_variablename_exception();
}
}
template<>
std::string variable_info_3<vit_const>::get_error_message() const
{
return "Cannot resolve variablename '" + this->name_ + "' for reading.";
}
template<>
std::string variable_info_3<vit_create_if_not_existent>::get_error_message() const
{
return "Cannot resolve variablename '" + this->name_ + "' for writing.";
}
template<>
std::string variable_info_3<vit_throw_if_not_existent>::get_error_message() const
{
return "Cannot resolve variablename '" + this->name_ + "' for writing without creating new childs.";
}
template<const variable_info_3_type vit>
void non_const_variable_info_3<vit>::clear(bool only_tables) const
{
this->throw_on_invalid();
return apply_visitor(clear_value_visitor<vit>(only_tables), this->state_);
}
template<const variable_info_3_type vit>
config::child_itors non_const_variable_info_3<vit>::append_array(std::vector<config> childs) const
{
this->throw_on_invalid();
return apply_visitor(as_range_visitor_base<vit,append_range_h>(append_range_h(childs)), this->state_);
}
template<const variable_info_3_type vit>
config::child_itors non_const_variable_info_3<vit>::insert_array(std::vector<config> childs) const
{
this->throw_on_invalid();
return apply_visitor(as_range_visitor_base<vit,insert_range_h>(insert_range_h(childs)), this->state_);
}
template<const variable_info_3_type vit>
config::child_itors non_const_variable_info_3<vit>::replace_array(std::vector<config> childs) const
{
this->throw_on_invalid();
return apply_visitor(as_range_visitor_base<vit,replace_range_h>(replace_range_h(childs)), this->state_);
}
template<const variable_info_3_type vit>
void non_const_variable_info_3<vit>::merge_array(std::vector<config> childs) const
{
this->throw_on_invalid();
apply_visitor(as_range_visitor_base<vit,merge_range_h>(merge_range_h(childs)), this->state_);
}
template<const variable_info_3_type vit>
bool variable_info_3<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_3_type vit>
bool variable_info_3<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_3<vit_const>;
template class variable_info_3<vit_create_if_not_existent>;
template class variable_info_3<vit_throw_if_not_existent>;
template class non_const_variable_info_3<vit_create_if_not_existent>;
template class non_const_variable_info_3<vit_throw_if_not_existent>;

111
src/variable_info.hpp Normal file
View file

@ -0,0 +1,111 @@
/*
Copyright (C) 2003 by David White <dave@whitevine.net>
Copyright (C) 2005 - 2014 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.
*/
/** Information on a WML variable. */
#include <string>
#include "config.hpp"
#include "variable_info_detail.hpp"
class invalid_variablename_exception : public std::exception
{
public:
invalid_variablename_exception() : std::exception() {}
const char* what() const throw()
{
return "invalid_variablename_exception";
}
};
template<const variable_info_3_detail::variable_info_3_type vit>
class variable_info_3
{
public:
typedef typename variable_info_3_detail::maybe_const<vit,config>::type t_config;
/// Doesn't throw
variable_info_3(const std::string& varname, t_config& vars);
~variable_info_3();
std::string get_error_message() const;
/// Doesn't throw
bool explicit_index() const;
/// might throw invalid_variablename_exception
bool exists_as_attribute() const;
/// might throw 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_3_detail::maybe_const<vit, config::attribute_value>::type &as_scalar() const;
/// might throw invalid_variablename_exception
typename variable_info_3_detail::maybe_const<vit, config>::type & as_container() const;
/// might throw invalid_variablename_exception
typename variable_info_3_detail::maybe_const<vit, config::child_itors>::type as_array() const; //range may be empty
protected:
std::string name_;
variable_info_3_detail::variable_info_3_state<vit> state_;
void throw_on_invalid() const;
bool valid_;
void calculate_value();
};
/// Extends variable_info_3 with methods that can only be applied if vit != vit_const
template<const variable_info_3_detail::variable_info_3_type vit>
class non_const_variable_info_3 : public variable_info_3<vit>, variable_info_3_detail::enable_if_non_const<vit>::type
{
public:
non_const_variable_info_3(const std::string& name, config& game_vars) : variable_info_3<vit>(name, game_vars) {};
~non_const_variable_info_3() {}
/// clears the vale this object points to
/// if only_tables = true it will not clear attribute values.
/// might throw 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;
};
/**
this variable accessor will create a childtable when resolving name if it doesnt exist yet.
*/
typedef non_const_variable_info_3<variable_info_3_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_3<variable_info_3_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_3<variable_info_3_detail::vit_const> variable_access_const;

View file

@ -0,0 +1,97 @@
/*
Copyright (C) 2003 by David White <dave@whitevine.net>
Copyright (C) 2005 - 2014 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.
*/
#ifndef VARIABLE_INFO_DETAIL_HPP_INCLUDED
#define VARIABLE_INFO_DETAIL_HPP_INCLUDED
#include <string>
#include "config.hpp"
namespace variable_info_3_detail
{
enum variable_info_3_type {vit_const, vit_create_if_not_existent, vit_throw_if_not_existent, };
enum variable_info_3_state_type {
state_start = 0, // for internal use
// only used at the 'starting_pos' of the variable_info_3::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.
};
//Special case of boost::enable_if
template<const variable_info_3_type vit>
struct enable_if_non_const
{
typedef enable_if_non_const<vit> type;
};
template<>
struct enable_if_non_const<vit_const>
{
};
template<const variable_info_3_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_3_type vit>
struct variable_info_3_state
{
typedef typename maybe_const<vit,config>::type t_child;
variable_info_3_state(t_child& 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:
// the current config is child_->child_at(key_, index_).
t_child* child_;
std::string key_;
int index_;
// If we have a temporary value like .length
// Then we store the result here.
config::attribute_value temp_val_;
// See the definition of 'variable_info_3_state_type'
variable_info_3_state_type type_;
};
}
#endif

0
stdout.txt Normal file
View file