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:
commit
fa28ffba0f
19 changed files with 1551 additions and 691 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 */
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()) {
|
||||
|
|
350
src/variable.cpp
350
src/variable.cpp
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
640
src/variable_info.cpp
Normal 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
111
src/variable_info.hpp
Normal 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;
|
97
src/variable_info_detail.hpp
Normal file
97
src/variable_info_detail.hpp
Normal 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
0
stdout.txt
Normal file
Loading…
Add table
Reference in a new issue