wesnoth/src/variable.cpp
Thonsew 88b5546118 Attempt to fix static de-initialization problems.
Some compilers are calling destructors for static objects before other
static objects use them in their destructors.  This patch initializes
static t_interned objects with new and intentionally doesn't call
delete in order to guarrantee their existence throughout the static
de-initialization phase.  It creates a new function
generate_safe_static_const_t_interned for this purpose.  It might
address bug #18666, although I can not duplicate the crash with gcc on
fedora 15.
2011-09-20 21:21:31 +00:00

916 lines
28 KiB
C++

/* $Id$ */
/*
Copyright (C) 2003 by David White <dave@whitevine.net>
Copyright (C) 2005 - 2011 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 "global.hpp"
#include "gettext.hpp"
#include "variable.hpp"
#include "foreach.hpp"
#include "formula_string_utils.hpp"
#include "gamestatus.hpp"
#include "log.hpp"
#include "resources.hpp"
#include "unit.hpp"
#include "unit_map.hpp"
#include "team.hpp"
#include "game_display.hpp" //for add_chat_message
#include <boost/variant.hpp>
#include <boost/unordered_set.hpp>
#include <boost/unordered_map.hpp>
#include "utils/lru_cache.hpp"
static lg::log_domain log_engine("engine");
#define LOG_NG LOG_STREAM(info, log_engine)
#define WRN_NG LOG_STREAM(warn, log_engine)
#define ERR_NG LOG_STREAM(err, log_engine)
static lg::log_domain log_wml("wml");
#define LOG_WML LOG_STREAM(info, log_wml)
#define WRN_WML LOG_STREAM(warn, log_wml)
#define ERR_WML LOG_STREAM(err, log_wml)
namespace
{
/**
* @todo FIXME: the variable repository should be
* a class of variable.hpp, and not the game_state.
*/
#define repos (resources::state_of_game)
// keeps track of insert_tag variables used by get_parsed_config
typedef boost::unordered_set<std::string> t_vconfig_recursion;
t_vconfig_recursion vconfig_recursion;
/**
* config_cache is a map to track temp storage of inserted tags on the heap.
* If an event is spawned from a variable or any ActionWML from a volatile
* source is to be executed safely then its memory should be managed by vconfig
*/
typedef boost::unordered_map<config const *, int> t_config_cache;
t_config_cache config_cache;
// map by hash for equivalent inserted tags already in the cache
typedef boost::unordered_map<std::string const *, config const *> t_hash_to_cache;
t_hash_to_cache hash_to_cache;
// map to remember config hashes that have already been calculated
typedef boost::unordered_map<config const *, std::string const *> t_config_hashes;
t_config_hashes config_hashes;
config empty_config;
struct compare_str_ptr {
bool operator()(const std::string* s1, const std::string* s2) const
{
return (*s1) < (*s2);
}
};
class hash_memory_manager {
public:
hash_memory_manager() :
mem_()
{
}
const std::string *find(const std::string& str) const {
// t_mem_::const_iterator itor = mem_.lower_bound(&str);
t_mem_::const_iterator itor = mem_.find(&str);
if(itor == mem_.end() || **itor != str) {
return NULL;
}
return *itor;
}
void insert(const std::string *newhash) {
mem_.insert(newhash);
}
void clear() {
hash_to_cache.clear();
config_hashes.clear();
t_mem_::iterator mem_it,
mem_end = mem_.end();
for(mem_it = mem_.begin(); mem_it != mem_end; ++mem_it) {
delete *mem_it;
}
mem_.clear();
}
~hash_memory_manager() {
clear();
}
private:
//typedef boost::unordered_set<std::string const*, compare_str_ptr> t_mem_;
typedef boost::unordered_set<std::string const*> t_mem_;
t_mem_ mem_;
};
hash_memory_manager hash_memory;
}
static const std::string* get_hash_of(const config* cp) {
//first see if the memory of a constant config hash exists
t_config_hashes::iterator ch_it = config_hashes.find(cp);
if(ch_it != config_hashes.end()) {
return ch_it->second;
}
//next see if an equivalent hash string has been memorized
const std::string & temp_hash = cp->hash();
std::string const* find_hash = hash_memory.find(temp_hash);
if(find_hash != NULL) {
return find_hash;
}
//finally, we just allocate a new hash string to memory
std::string* new_hash = new std::string(temp_hash);
hash_memory.insert(new_hash);
//do not insert into config_hashes (may be a variable config)
return new_hash;
}
static void increment_config_usage(const config*& key) {
if(key == NULL) return;
t_config_cache::iterator this_usage = config_cache.find(key);
if(this_usage != config_cache.end()) {
++this_usage->second;
return;
}
const std::string *hash = get_hash_of(key);
const config *& cfg_store = hash_to_cache[hash];
if(cfg_store == NULL || (key != cfg_store && *key != *cfg_store)) {
// this is a new volatile config: allocate some memory & update key
key = new config(*key);
// remember this cache to prevent an equivalent one from being created
cfg_store = key;
// since the config is now constant, we can safely memorize the hash
config_hashes[key] = hash;
} else {
// swap the key with an equivalent or equal one in the cache
key = cfg_store;
}
++(config_cache[key]);
}
static void decrement_config_usage(const config* key) {
if(key == NULL) return;
t_config_cache::iterator this_usage = config_cache.find(key);
assert(this_usage != config_cache.end());
if(--(this_usage->second) == 0) {
config_cache.erase(this_usage);
if(config_cache.empty()) {
hash_memory.clear();
} else {
if(!hash_to_cache.empty()) {
hash_to_cache.erase(get_hash_of(key));
}
config_hashes.erase(key);
}
delete key;
}
}
vconfig::vconfig() :
cfg_(NULL), cache_key_(NULL)
{
}
vconfig::vconfig(const config* cfg, const config * cache_key) :
cfg_(cfg), cache_key_(cache_key)
{
increment_config_usage(cache_key_);
if(cache_key_ != cache_key) {
//location of volatile cfg has moved
cfg_ = cache_key_;
}
}
vconfig::vconfig(const config &cfg, bool is_volatile) :
cfg_(&cfg), cache_key_(&cfg)
{
if(is_volatile) {
increment_config_usage(cache_key_);
if (cache_key_ != &cfg) {
//location of volatile cfg has moved
cfg_ = cache_key_;
}
} else {
cache_key_ = NULL;
}
}
vconfig::vconfig(const vconfig& v) :
cfg_(v.cfg_), cache_key_(v.cache_key_)
{
increment_config_usage(cache_key_);
}
vconfig::~vconfig()
{
decrement_config_usage(cache_key_);
}
vconfig vconfig::empty_vconfig()
{
return vconfig(config(), true);
}
vconfig vconfig::unconstructed_vconfig()
{
return vconfig();
}
vconfig& vconfig::operator=(const vconfig& cfg)
{
const config* prev_key = cache_key_;
cfg_ = cfg.cfg_;
cache_key_ = cfg.cache_key_;
increment_config_usage(cache_key_);
decrement_config_usage(prev_key);
return *this;
}
const config vconfig::get_parsed_config() const {
static const config::t_token & z_insert_tag( generate_safe_static_const_t_interned(n_token::t_token("insert_tag")) );
static const config::t_token & z_name( generate_safe_static_const_t_interned(n_token::t_token("name")) );
static const config::t_token & z_variable( generate_safe_static_const_t_interned(n_token::t_token("variable")) );
config res;
foreach (const config::attribute &i, cfg_->attribute_range()) {
res[i.first] = expand(i.first);
}
foreach (const config::any_child &child, cfg_->all_children_range())
{
if (child.key == z_insert_tag) {
vconfig insert_cfg(child.cfg);
const config::attribute_value& name = insert_cfg[z_name];
const config::attribute_value& vname = insert_cfg[z_variable];
if(!vconfig_recursion.insert(vname).second) {
throw recursion_error("vconfig::get_parsed_config() infinite recursion detected, aborting");
}
try {
variable_info vinfo(vname.token(), false, variable_info::TYPE_CONTAINER);
if(!vinfo.is_valid()) {
res.add_child(name.token()); //add empty tag
} else if(vinfo.is_explicit_index()) {
res.add_child(name.token(), vconfig(vinfo.as_container()).get_parsed_config());
} else {
variable_info::array_range range = vinfo.as_array();
if(range.first == range.second) {
res.add_child(name.token()); //add empty tag
}
while(range.first != range.second) {
res.add_child(name.token(), vconfig(*range.first++).get_parsed_config());
}
}
vconfig_recursion.erase(vname);
} catch(recursion_error &err) {
vconfig_recursion.erase(vname);
WRN_NG << err.message << std::endl;
if(vconfig_recursion.empty()) {
res.add_child(z_insert_tag, insert_cfg.get_config());
} else {
// throw to the top [insert_tag] which started the recursion
throw;
}
}
} else {
res.add_child(child.key, vconfig(child.cfg).get_parsed_config());
}
}
return res;
}
vconfig::child_list vconfig::get_children(const config::t_token& key) const {
static const config::t_token & z_insert_tag( generate_safe_static_const_t_interned(n_token::t_token("insert_tag")) );
static const config::t_token & z_name( generate_safe_static_const_t_interned(n_token::t_token("name")) );
static const config::t_token & z_variable( generate_safe_static_const_t_interned(n_token::t_token("variable")) );
vconfig::child_list res;
foreach (const config::any_child &child, cfg_->all_children_range())
{
if (child.key == key) {
res.push_back(vconfig(&child.cfg, cache_key_));
} else if (child.key == z_insert_tag) {
vconfig insert_cfg(child.cfg);
if(insert_cfg[z_name] == key) {
variable_info vinfo(insert_cfg[z_variable].token(), false, variable_info::TYPE_CONTAINER);
if(!vinfo.is_valid()) {
//push back an empty tag
res.push_back(vconfig(empty_config));
} else if(vinfo.is_explicit_index()) {
config * cp = &(vinfo.as_container());
res.push_back(vconfig(cp, cp));
} else {
variable_info::array_range range = vinfo.as_array();
if(range.first == range.second) {
//push back an empty tag
res.push_back(vconfig(empty_config));
}
while(range.first != range.second) {
config *cp = &*range.first++;
res.push_back(vconfig(cp, cp));
}
}
}
}
}
return res;
}
vconfig::child_list vconfig::get_children(const std::string& key) const {return get_children(t_token(key));}
vconfig vconfig::child(const config::t_token& key) const
{
static const config::t_token & z_insert_tag( generate_safe_static_const_t_interned(n_token::t_token("insert_tag")) );
static const config::t_token & z_name( generate_safe_static_const_t_interned(n_token::t_token("name")) );
static const config::t_token & z_variable( generate_safe_static_const_t_interned(n_token::t_token("variable")) );
if (const config &natural = cfg_->child(key)) {
return vconfig(&natural, cache_key_);
}
foreach (const config &ins, cfg_->child_range(z_insert_tag))
{
vconfig insert_cfg(ins);
if(insert_cfg[z_name] == key) {
variable_info vinfo(insert_cfg[z_variable].token(), false, variable_info::TYPE_CONTAINER);
if(!vinfo.is_valid()) {
return vconfig(empty_config);
}
config * cp = &(vinfo.as_container());
return vconfig(cp, cp);
}
}
return unconstructed_vconfig();
}
vconfig vconfig::child(const std::string& key) const {return child(t_token(key));}
bool vconfig::has_child(const config::t_token& key) const
{
static const config::t_token & z_insert_tag( generate_safe_static_const_t_interned(n_token::t_token("insert_tag")) );
static const config::t_token & z_name( generate_safe_static_const_t_interned(n_token::t_token("name")) );
if (cfg_->child(key)) {
return true;
}
foreach (const config &ins, cfg_->child_range(z_insert_tag))
{
vconfig insert_cfg(ins);
if(insert_cfg[z_name] == key) {
return true;
}
}
return false;
}
bool vconfig::has_child(const std::string& key) const {return has_child(t_token(key));}
struct vconfig_expand_visitor : public config::attribute_value::default_visitor {
using default_visitor::operator();
config::attribute_value & val;
vconfig_expand_visitor(config::attribute_value & v):val(v){}
void operator()(config::t_token const & token){
config::t_token cp(token);
utils::interpolate_variables_into_token(cp, *repos);
val = cp;
}
void operator()(const t_string &s){
val = utils::interpolate_variables_into_tstring(s, *repos);
}
};
config::attribute_value vconfig::expand(const config::t_token &key) const {
config::attribute_value val = (*cfg_)[key];
if (repos) {
vconfig_expand_visitor visitor(val);
val.apply_visitor(visitor);
}
return val;
}
config::attribute_value vconfig::expand(const std::string &key) const {return operator[](t_token(key));}
vconfig::all_children_iterator::all_children_iterator(const Itor &i, const config *cache_key)
: i_(i), inner_index_(0), cache_key_(cache_key)
{
}
vconfig::all_children_iterator& vconfig::all_children_iterator::operator++() {
static const config::t_token & z_insert_tag( generate_safe_static_const_t_interned(n_token::t_token("insert_tag")) );
static const config::t_token & z_variable( generate_safe_static_const_t_interned(n_token::t_token("variable")) );
if (inner_index_ >= 0 && i_->key == z_insert_tag)
{
variable_info vinfo(vconfig(i_->cfg)[z_variable].token(), false, variable_info::TYPE_CONTAINER);
if(vinfo.is_valid() && !vinfo.is_explicit_index()) {
variable_info::array_range range = vinfo.as_array();
if (++inner_index_ < std::distance(range.first, range.second)) {
return *this;
}
inner_index_ = 0;
}
}
++i_;
return *this;
}
vconfig::all_children_iterator vconfig::all_children_iterator::operator++(int)
{
vconfig::all_children_iterator i = *this;
this->operator++();
return i;
}
vconfig::all_children_iterator::reference vconfig::all_children_iterator::operator*() const
{
return value_type(get_key(), get_child());
}
vconfig::all_children_iterator::pointer vconfig::all_children_iterator::operator->() const
{
pointer_proxy p = { value_type(get_key(), get_child()) };
return p;
}
config::t_token vconfig::all_children_iterator::get_key() const
{
static const config::t_token & z_insert_tag( generate_safe_static_const_t_interned(n_token::t_token("insert_tag")) );
static const config::t_token & z_name( generate_safe_static_const_t_interned(n_token::t_token("name")) );
const config::t_token &key = i_->key;
if (inner_index_ >= 0 && key == z_insert_tag) {
config::attribute_value const & xx= vconfig(i_->cfg)[z_name];
return xx.token();
}
return key;
}
vconfig vconfig::all_children_iterator::get_child() const
{
static const config::t_token & z_insert_tag( generate_safe_static_const_t_interned(n_token::t_token("insert_tag")) );
static const config::t_token & z_variable( generate_safe_static_const_t_interned(n_token::t_token("variable")) );
if (inner_index_ >= 0 && i_->key == z_insert_tag)
{
config * cp;
variable_info vinfo(vconfig(i_->cfg)[z_variable].token(), false, variable_info::TYPE_CONTAINER);
if(!vinfo.is_valid()) {
return vconfig(empty_config);
} else if(inner_index_ == 0) {
cp = &(vinfo.as_container());
return vconfig(cp, cp);
}
variable_info::array_range r = vinfo.as_array();
std::advance(r.first, inner_index_);
cp = &*r.first;
return vconfig(cp, cp);
}
return vconfig(&i_->cfg, cache_key_);
}
bool vconfig::all_children_iterator::operator==(const all_children_iterator &i) const
{
return i_ == i.i_ && inner_index_ == i.inner_index_;
}
vconfig::all_children_iterator vconfig::ordered_begin() const
{
return all_children_iterator(cfg_->ordered_begin(), cache_key_);
}
vconfig::all_children_iterator vconfig::ordered_end() const
{
return all_children_iterator(cfg_->ordered_end(), cache_key_);
}
namespace variable
{
manager::~manager()
{
hash_memory.clear();
}
}
scoped_wml_variable::scoped_wml_variable(const std::string& var_name) :
previous_val_(), var_name_(var_name), activated_(false) {
repos->scoped_variables.push_back(this);
}
scoped_wml_variable::scoped_wml_variable(const config::t_token& var_name) :
previous_val_(), var_name_(var_name), activated_(false) {
repos->scoped_variables.push_back(this);
}
config &scoped_wml_variable::store(const config &var_value)
{
foreach (const config &i, repos->get_variables().child_range(var_name_)) {
previous_val_.add_child(var_name_, i);
}
repos->clear_variable_cfg(var_name_);
config &res = repos->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;
}
scoped_wml_variable::~scoped_wml_variable()
{
if(activated_) {
repos->clear_variable_cfg(var_name_);
foreach (const config &i, previous_val_.child_range(var_name_)) {
repos->add_variable_cfg(var_name_, i);
}
LOG_NG << "scoped_wml_variable: var_name \"" << var_name_ << "\" has been reverted.\n";
}
assert(repos->scoped_variables.back() == this);
repos->scoped_variables.pop_back();
}
void scoped_xy_unit::activate()
{
static const config::t_token & z_x( generate_safe_static_const_t_interned(n_token::t_token("x")) );
static const config::t_token & z_y( generate_safe_static_const_t_interned(n_token::t_token("y")) );
map_location loc = map_location(x_, y_);
unit_map::const_iterator itor = umap_.find(loc);
if(itor != umap_.end()) {
config &tmp_cfg = store();
itor->write(tmp_cfg);
tmp_cfg[z_x] = x_ + 1;
tmp_cfg[z_y] = y_ + 1;
LOG_NG << "auto-storing $" << name() << " at (" << loc << ")\n";
} else {
ERR_NG << "failed to auto-store $" << name() << " at (" << loc << ")\n";
}
}
void scoped_weapon_info::activate()
{
if (data_) {
store(data_);
}
}
void scoped_recall_unit::activate()
{
static const config::t_token & z_x( generate_safe_static_const_t_interned(n_token::t_token("x")) );
static const config::t_token & z_y( generate_safe_static_const_t_interned(n_token::t_token("y")) );
static const config::t_token & z_recall( generate_safe_static_const_t_interned(n_token::t_token("recall")) );
const t_teams& teams = teams_manager::get_teams();
t_teams::const_iterator team_it;
for (team_it = teams.begin(); team_it != teams.end(); ++team_it) {
if (team_it->save_id() == player_ )
break;
}
if(team_it != teams.end()) {
if(team_it->recall_list().size() > recall_index_) {
config &tmp_cfg = store();
team_it->recall_list()[recall_index_].write(tmp_cfg);
tmp_cfg[z_x] = z_recall;
tmp_cfg[z_y] = z_recall;
LOG_NG << "auto-storing $" << name() << " for player: " << player_
<< " at recall index: " << recall_index_ << '\n';
store(tmp_cfg);
} else {
ERR_NG << "failed to auto-store $" << name() << " for player: " << player_
<< " at recall index: " << recall_index_ << '\n';
}
} else {
ERR_NG << "failed to auto-store $" << name() << " for player: " << player_ << '\n';
}
}
namespace {
typedef config::t_token t_token;
struct t_parsed {
static const int NO_INDEX = INT_MAX;
t_parsed(t_token const & a, int i = NO_INDEX):token(a),index(i){}
t_parsed(t_parsed const & a):token(a.token), index(a.index){}
operator t_token const () const {
if(index == NO_INDEX){
return token; }
else {
std::stringstream ss;
ss << token << '[' <<index << "].";
return t_token(ss.str()); }
}
t_token token;
int index; };
typedef std::vector<t_parsed> t_parsed_tokens;
typedef std::vector<t_token> t_tokens;
class t_parse_token {
public:
t_parsed_tokens operator()(config::t_token const & key) const {
//an example varname is "unit_store.modifications.trait[0]"
t_parsed_tokens parsed_tokens;
std::string const & skey(key);
std::size_t i(0), i_start_of_token(0);
std::size_t iend(skey.size());
bool is_lbrack(false);
while(i != iend){
char c = skey[i];
if(i == i_start_of_token){
switch(c){
case '.' :
case '[':
case ']':
throw game::wml_syntax_error(skey, i, _("the first character of the identifier is an invalid character, one of . , [, or ]. ") );
}
}
if(is_lbrack){
switch(c){
case '.' :
case '[':
throw game::wml_syntax_error(skey, i, _("there is a dot . or left bracket [ after a left bracket [ starting the variable name"));
break;
case ']':
std::string index_str(skey.substr(i_start_of_token, i - i_start_of_token ));
std::istringstream is(index_str);
size_t index;
is >> index;
if(index > game_config::max_loop) {
ERR_NG << "variable_info: index greater than " << game_config::max_loop << ", truncated\n";
index = game_config::max_loop;
}
parsed_tokens.back().index = index;
//adjust for dot after lbrack as in "unit_store.modifications.trait[0].y"
if (i < (iend-1) ) {
char next_c = skey[i+1] ;
if(next_c == '.'){
++i; } }
i_start_of_token = i + 1 ;
is_lbrack=false;
break;
}
} else {
switch(c){
case '.' :
parsed_tokens.push_back(t_parsed(t_token(skey.substr(i_start_of_token, i - i_start_of_token ))));
i_start_of_token = i + 1;
break;
case '[':
parsed_tokens.push_back(t_parsed(t_token(skey.substr(i_start_of_token, i - i_start_of_token ))));
i_start_of_token = i + 1;
is_lbrack=true;
break;
case ']':
break;
}
}
++i;
}
if(i_start_of_token != i){
parsed_tokens.push_back(t_token(skey.substr(i_start_of_token, i - i_start_of_token )));
}
return parsed_tokens;
}
};
// typedef boost::unordered_map<config::t_token, t_parsed_tokens> t_all_parsed;
static const unsigned int CACHE_SIZE = 10000;
typedef n_lru_cache::t_lru_cache<config::t_token, t_parsed_tokens, t_parse_token> t_all_parsed;
bool recursive_activation = false;
/** Turns on any auto-stored variables */
void activate_scope_variable(t_parsed_tokens const & tokens)
{
if(recursive_activation){ return; }
if(! tokens.empty()){
config::t_token const & first((tokens.begin()->token));
std::vector<scoped_wml_variable*>::reverse_iterator rit;
for(rit = repos->scoped_variables.rbegin(); rit != repos->scoped_variables.rend(); ++rit) {
if((**rit).name() == first) {
recursive_activation = true;
if(!(**rit).activated()) {
(**rit).activate();
}
recursive_activation = false;
break;
}
}
}
}
} //end namespace
void variable_info::init(const config::t_token& varname, bool force_valid) {
static const config::t_token & z_length( generate_safe_static_const_t_interned(n_token::t_token("length")) );
static const config::t_token & z___array( generate_safe_static_const_t_interned(n_token::t_token("__array")) );
static const config::t_token & z___value( generate_safe_static_const_t_interned(n_token::t_token("__value")) );
try {
//an example varname is "unit_store.modifications.trait[0]"
assert(repos != NULL);
static t_all_parsed cache( t_parse_token(), CACHE_SIZE);
t_parsed_tokens tokens(cache.check(varname));
if(tokens.empty()){ return; }
activate_scope_variable(tokens);
vars = &repos->variables_;
t_parsed_tokens::iterator i(tokens.begin()), last_token(tokens.end())
, second_last_token(tokens.end()), i_array_name, i_maybe_tail(i);
if(!tokens.empty()) { --last_token; }
if(tokens.size() > 1) { second_last_token-=2; }
//process subvars
while (i < last_token){
int inner_index = 0;
int size = vars->child_count( i->token);
if(i->index != t_parsed::NO_INDEX){
inner_index = i->index;
if(size <= inner_index) {
bool last_key_is_not_length ((i != second_last_token) || (last_token->token != z_length));
if(force_valid) {
// Add elements to the array until the requested size is attained
if( last_key_is_not_length) {
for(; size <= inner_index; ++size) {
vars->add_child(i->token);
}
}
} else if(inner_index != 0) {
throw game::wml_syntax_error(tokens, i - tokens.begin(),
_("the WML array index is larger than the largest array element.") );
} else if( last_key_is_not_length ) {
throw game::wml_syntax_error(tokens, i - tokens.begin() , _("the WML array index is invalid. Use varname[0].length to check for the existence of an array element.") );
} //else return length 0 for non-existent WML array (handled below)
}
vars = &vars->child(i->token, inner_index);
} else {
if( i == second_last_token && last_token->token == z_length){
switch(vartype) {
case variable_info::TYPE_ARRAY:
case variable_info::TYPE_CONTAINER:
throw game::wml_syntax_error(tokens, i - tokens.begin()
, _("attempt to get length of a non array/container.") );
case variable_info::TYPE_SCALAR:
default:
// Store the length of the array as a temporary variable
repos->temporaries_[varname] = int(size);
is_valid_ = true;
break;
}
key = varname;
vars = &repos->temporaries_;
return;
}
config *ovars(vars);
vars = &vars->child(i->token, inner_index);
//todo fix the config operator bool and change (vars == NULL)to !vars
if(vars == NULL){
if(force_valid) {
vars = &ovars->add_child(i->token); }
else {
is_valid_ = false;
return; } }
}
++i;
}
//Process the last token
key = i->token;
if(i->index != t_parsed::NO_INDEX && i->index != 0){
explicit_index_ = true;
size_t size = vars->child_count(key);
index = i->index;
if(size <= index) {
if(!force_valid) {
throw game::wml_syntax_error(tokens, i - tokens.begin(),
_("the WML array index is larger than the largest array element.") );
}
for(; size <= index; ++size) {
vars->add_child(key);
}
}
switch(vartype) {
case variable_info::TYPE_ARRAY:
vars = &vars->child(key, index);
key = z___array;
is_valid_ = force_valid || vars->child(key);
break;
case variable_info::TYPE_SCALAR:
vars = &vars->child(key, index);
key = z___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;
}
}
} catch (game::wml_syntax_error & e) {
WRN_NG << e.what()<<"\n";
is_valid_ = false;
}
}
variable_info::variable_info(const config::t_token& varname, bool force_valid, TYPE validation_type)
: vartype(validation_type), is_valid_(false), explicit_index_(false), key(varname), index(0), vars(NULL) {
init( varname, force_valid) ;}
variable_info::variable_info(const t_string& varname, bool force_valid, TYPE validation_type)
: vartype(validation_type), is_valid_(false), explicit_index_(false),key(varname.token()), index(0), vars(NULL) {
init(varname.token(), force_valid);}
variable_info::variable_info(const std::string& varname, bool force_valid, TYPE validation_type)
: vartype(validation_type), is_valid_(false), explicit_index_(false), key(varname), index(0), vars(NULL) {
init(config::t_token(varname), force_valid);}
variable_info::variable_info(const config::attribute_value& varname, bool force_valid, TYPE validation_type)
: vartype(validation_type), is_valid_(false), explicit_index_(false), key(varname.token()), index(0), vars(NULL) {
init(varname.token(), force_valid);}
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);
}