Support [switch] and [if] in [tag]

Use this new feature to validate [effect] tags
This commit is contained in:
Celtic Minstrel 2018-03-15 23:16:48 -04:00
parent 06c13291c2
commit fb30b594b6
9 changed files with 483 additions and 38 deletions

View file

@ -1,5 +1,3 @@
#textdomain wesnoth
{./macros.cfg}
[wml_schema]
@ -161,6 +159,21 @@
name="server_address"
value="[_a-zA-Z0-9-]+(\.[_a-zA-Z0-9-]+)+:\d+"
[/type]
[type]
name="effect_times"
[union]
[type]
link=int
[/type]
[type]
value="per level"
[/type]
[/union]
[/type]
[type]
name="effect_set_special_mode"
value="append|replace"
[/type]
[tag]
name="root"
min=1

View file

@ -79,6 +79,7 @@
[/tag]
# Specific animation tags
# Any new tag added here must also be linked from [effect]
[tag]
name="animation"
max=infinite
@ -121,11 +122,6 @@
max=infinite
super="units/unit_type/$animation"
[/tag]
[tag]
name="defend"
max=infinite
super="units/unit_type/$animation"
[/tag]
[tag]
name="pre_movement_anim"
max=infinite

View file

@ -5,10 +5,215 @@
[tag]
name="effect"
max=infinite
# TODO: The schema of this tag depends on the value of apply_to...
{REQUIRED_KEY apply_to string}
{ANY_KEY string}
any_tag=yes
{DEFAULT_KEY times effect_times 1}
{FILTER_TAG "filter" unit ()}
[switch]
key=apply_to
[case]
value=new_attack
super="units/unit_type/attack"
[/case]
{FILTER_TAG "case" weapon value=remove_attacks}
{FILTER_TAG "case" weapon (
value=attack
{SIMPLE_KEY set_name string}
{SIMPLE_KEY set_description t_string}
{SIMPLE_KEY set_type string}
{SIMPLE_KEY set_icon string}
{SIMPLE_KEY set_range string}
{SIMPLE_KEY set_damage int}
{SIMPLE_KEY set_attacks int}
{SIMPLE_KEY set_parry int}
{SIMPLE_KEY set_accuracy int}
{SIMPLE_KEY set_movement_used int}
{SIMPLE_KEY increase_damage int}
{SIMPLE_KEY increase_attacks int}
{SIMPLE_KEY increase_parry int}
{SIMPLE_KEY increase_accuracy int}
{SIMPLE_KEY increase_movement_used int}
{SIMPLE_KEY attack_weight int}
{SIMPLE_KEY defense_weight int}
{SIMPLE_KEY remove_specials string_list}
[tag]
name="set_specials"
super="units/unit_type/attack/specials"
{DEFAULT_KEY mode effect_set_special_mode replace}
[/tag]
)}
[case]
value=max_attacks,max_experience
{SIMPLE_KEY increase int_percent}
[/case]
[case]
value=movement,vision,jamming,experience,recall_cost
{SIMPLE_KEY increase int_percent}
{SIMPLE_KEY set int}
[/case]
[case]
value=hitpoints
{SIMPLE_KEY increase int_percent}
{DEFAULT_KEY heal_full bool yes}
{SIMPLE_KEY increase_total int_percent}
{DEFAULT_KEY violate_maximum bool no}
[/case]
[case]
value=loyal
# Nothing allowed here
[/case]
[case]
value=movement_costs
{SIMPLE_KEY replace bool}
[link]
name="units/movetype/movement_costs"
[/link]
[/case]
[case]
value=vision_costs
{SIMPLE_KEY replace bool}
[link]
name="units/movetype/vision_costs"
[/link]
[/case]
[case]
value=jamming_costs
{SIMPLE_KEY replace bool}
[link]
name="units/movetype/jamming_costs"
[/link]
[/case]
[case]
value=defense
{SIMPLE_KEY replace bool}
[link]
name="units/movetype/defense"
[/link]
[/case]
[case]
value=resistance
{SIMPLE_KEY replace bool}
[link]
name="units/movetype/resistance"
[/link]
[/case]
[case]
value=variation,type
{SIMPLE_KEY name string}
[/case]
[case]
value=status
{SIMPLE_KEY add string_list}
{SIMPLE_KEY remove string_list}
[/case]
[case]
value=zoc
{SIMPLE_KEY value bool}
[/case]
[case]
value=profile
{SIMPLE_KEY portrait string}
{SIMPLE_KEY small_portrait string}
{SIMPLE_KEY description t_string}
[/case]
[case]
value=new_ability,remove_ability
[link]
name="units/unit_type/abilities"
[/link]
[/case]
[case]
value=new_animation
[link]
name="units/unit_type/animation"
[/link]
[link]
name="units/unit_type/defend"
[/link]
[link]
name="units/unit_type/death"
[/link]
[link]
name="units/unit_type/standing_anim"
[/link]
[link]
name="units/unit_type/movement_anim"
[/link]
[link]
name="units/unit_type/idle_anim"
[/link]
[link]
name="units/unit_type/attack_anim"
[/link]
[link]
name="units/unit_type/victory_anim"
[/link]
[link]
name="units/unit_type/pre_movement_anim"
[/link]
[link]
name="units/unit_type/post_movement_anim"
[/link]
[link]
name="units/unit_type/draw_weapon_anim"
[/link]
[link]
name="units/unit_type/sheath_weapon_anim"
[/link]
[link]
name="units/unit_type/leading_anim"
[/link]
[link]
name="units/unit_type/recruit_anim"
[/link]
[link]
name="units/unit_type/recruiting_anim"
[/link]
[link]
name="units/unit_type/healing_anim"
[/link]
[link]
name="units/unit_type/extra_anim"
[/link]
[/case]
[case]
value=image_mod,overlay
{SIMPLE_KEY replace string}
{SIMPLE_KEY add string}
[/case]
[case]
value=ellipse
{SIMPLE_KEY ellipse string}
[/case]
[case]
value=halo
{SIMPLE_KEY halo string}
[/case]
[case]
value=alignment
{SIMPLE_KEY set alignment}
[/case]
[case]
value=new_advancement
{SIMPLE_KEY replace bool}
{SIMPLE_KEY types string_list}
[link]
name="units/$modifications/advancement"
[/link]
[/case]
[case]
value=remove_advancement
{SIMPLE_KEY types string_list}
{SIMPLE_KEY amlas string_list}
[/case]
[default]
any_tag=yes
{ANY_KEY string}
[/default]
[/switch]
{WML_MERGE_KEYS}
[/tag]
{WML_MERGE_KEYS}

View file

@ -184,6 +184,7 @@ void parser::parse_element()
std::string elname;
config* current_element = nullptr;
config* parent = nullptr;
switch(tok_.current_token().type) {
case token::STRING: // [element]
@ -194,11 +195,12 @@ void parser::parse_element()
}
// Add the element
current_element = &(elements.top().cfg->add_child(elname));
parent = elements.top().cfg;
current_element = &(parent->add_child(elname));
elements.emplace(current_element, elname, tok_.get_start_line(), tok_.get_file());
if(validator_) {
validator_->open_tag(elname, tok_.get_start_line(), tok_.get_file());
validator_->open_tag(elname, *parent, tok_.get_start_line(), tok_.get_file());
}
break;
@ -215,17 +217,18 @@ void parser::parse_element()
}
// Find the last child of the current element whose name is element
if(config& c = elements.top().cfg->child(elname, -1)) {
parent = elements.top().cfg;
if(config& c = parent->child(elname, -1)) {
current_element = &c;
if(validator_) {
validator_->open_tag(elname, tok_.get_start_line(), tok_.get_file(), true);
validator_->open_tag(elname, *parent, tok_.get_start_line(), tok_.get_file(), true);
}
} else {
current_element = &elements.top().cfg->add_child(elname);
current_element = &parent->add_child(elname);
if(validator_) {
validator_->open_tag(elname, tok_.get_start_line(), tok_.get_file());
validator_->open_tag(elname, *parent, tok_.get_start_line(), tok_.get_file());
}
}

View file

@ -189,13 +189,13 @@ bool schema_validator::read_config_file(const std::string& filename)
* assume they all are on their place due to parser algorithm
* and validation logic
*/
void schema_validator::open_tag(const std::string& name, int start_line, const std::string& file, bool addittion)
void schema_validator::open_tag(const std::string& name, const config& parent, int start_line, const std::string& file, bool addittion)
{
if(!stack_.empty()) {
const class_tag* tag = nullptr;
if(stack_.top()) {
tag = stack_.top()->find_tag(name, root_);
tag = stack_.top()->find_tag(name, root_, parent);
if(!tag) {
wrong_tag_error(file, start_line, name, stack_.top()->get_name(), create_exceptions_);
@ -243,7 +243,7 @@ void schema_validator::validate(const config& cfg, const std::string& name, int
// Please note that validating unknown tag keys the result will be false
// Checking all elements counters.
if(!stack_.empty() && stack_.top() && config_read_) {
for(const auto& tag : stack_.top()->tags()) {
for(const auto& tag : stack_.top()->tags(cfg)) {
int cnt = counter_.top()[tag.first].cnt;
if(tag.second.get_min() > cnt) {
@ -259,7 +259,7 @@ void schema_validator::validate(const config& cfg, const std::string& name, int
}
// Checking if all mandatory keys are present
for(const auto& key : stack_.top()->keys()) {
for(const auto& key : stack_.top()->keys(cfg)) {
if(key.second.is_mandatory()) {
if(cfg.get(key.first) == nullptr) {
cache_.top()[&cfg].emplace_back(MISSING_KEY, file, start_line, 0, name, key.first);
@ -274,7 +274,7 @@ void schema_validator::validate_key(
{
if(!stack_.empty() && stack_.top() && !stack_.top()->get_name().empty() && config_read_) {
// checking existing keys
const class_key* key = stack_.top()->find_key(name);
const class_key* key = stack_.top()->find_key(name, cfg);
if(key) {
bool matched = false;
for(auto& possible_type : utils::split(key->get_type())) {

View file

@ -52,7 +52,7 @@ public:
}
virtual void open_tag(
const std::string& name, int start_line = 0, const std::string& file = "", bool addittion = false);
const std::string& name, const config& parent, int start_line = 0, const std::string& file = "", bool addittion = false);
virtual void close_tag();
virtual void validate(const config& cfg, const std::string& name, int start_line, const std::string& file);
virtual void validate_key(const config& cfg,

View file

@ -20,6 +20,7 @@
#include "serialization/tag.hpp"
#include "serialization/string_utils.hpp"
#include "boost/optional.hpp"
#include "formatter.hpp"
#include "config.hpp"
@ -153,6 +154,8 @@ void class_key::print(std::ostream& os, int level) const
os << s << " default=" << default_ << "\n";
}
// TODO: Other attributes
os << s << "[/key]\n";
}
@ -189,6 +192,14 @@ class_tag::class_tag(const config& cfg)
std::string link_name = link["name"].str();
add_link(link_name);
}
for(const config& sw : cfg.child_range("switch")) {
add_switch(sw);
}
for(const config& filter : cfg.child_range("if")) {
add_filter(filter);
}
}
void class_tag::print(std::ostream& os)
@ -204,12 +215,23 @@ void class_tag::add_link(const std::string& link)
links_.emplace(name_link, link);
}
const class_key* class_tag::find_key(const std::string& name) const
const class_key* class_tag::find_key(const std::string& name, const config& match) const
{
// Check the conditions first, so that conditional definitions
// override base definitions in the event of duplicates.
for(auto& cond : conditions_) {
if(cond.matches(match)) {
if(auto key = cond.find_key(name, match)) {
return key;
}
}
}
const auto it_keys = keys_.find(name);
if(it_keys != keys_.end()) {
return &(it_keys->second);
}
key_map::const_iterator it_fuzzy = std::find_if(keys_.begin(), keys_.end(), [&name](const key_map::value_type& key){
if(!key.second.is_fuzzy()) {
return false;
@ -232,7 +254,7 @@ const std::string* class_tag::find_link(const std::string& name) const
return nullptr;
}
const class_tag* class_tag::find_tag(const std::string& fullpath, const class_tag& root) const
const class_tag* class_tag::find_tag(const std::string& fullpath, const class_tag& root, const config& match) const
{
if(fullpath.empty()) {
return nullptr;
@ -249,18 +271,28 @@ const class_tag* class_tag::find_tag(const std::string& fullpath, const class_ta
name = fullpath;
}
// Check the conditions first, so that conditional definitions
// override base definitions in the event of duplicates.
for(auto& cond : conditions_) {
if(cond.matches(match)) {
if(auto tag = cond.find_tag(fullpath, root, match)) {
return tag;
}
}
}
const auto it_tags = tags_.find(name);
if(it_tags != tags_.end()) {
if(next_path.empty()) {
return &(it_tags->second);
} else {
return it_tags->second.find_tag(next_path, root);
return it_tags->second.find_tag(next_path, root, match);
}
}
const auto it_links = links_.find(name);
if(it_links != links_.end()) {
return root.find_tag(it_links->second + "/" + next_path, root);
return root.find_tag(it_links->second + "/" + next_path, root, match);
}
const auto it_fuzzy = std::find_if(tags_.begin(), tags_.end(), [&name](const tag_map::value_type& tag){
@ -273,7 +305,7 @@ const class_tag* class_tag::find_tag(const std::string& fullpath, const class_ta
if(next_path.empty()) {
return &(it_fuzzy->second);
} else {
return it_tags->second.find_tag(next_path, root);
return it_tags->second.find_tag(next_path, root, match);
}
}
@ -290,6 +322,10 @@ void class_tag::expand_all(class_tag& root)
tag.second.expand(root);
tag.second.expand_all(root);
}
for(auto& cond : conditions_) {
cond.expand(root);
cond.expand_all(root);
}
}
void class_tag::remove_keys_by_type(const std::string& type)
@ -366,6 +402,8 @@ void class_tag::printl(std::ostream& os, int level, int step)
key.second.print(os, level + step);
}
// TODO: Other attributes
os << s << "[/tag]\n";
}
@ -382,6 +420,7 @@ void class_tag::add_tag(const std::string& path, const class_tag& tag, class_tag
it->second.add_tags(tag.tags_);
it->second.add_keys(tag.keys_);
it->second.add_links(tag.links_);
// TODO: Other attributes
}
links_.erase(tag.get_name());
@ -411,8 +450,10 @@ void class_tag::add_tag(const std::string& path, const class_tag& tag, class_tag
void class_tag::append_super(const class_tag& tag, const std::string& path)
{
// TODO: Ensure derived tag definitions override base tag definitions in the event of duplicates
add_keys(tag.keys_);
add_links(tag.links_);
add_conditions(tag.conditions_);
for(const auto& t : tag.tags_) {
links_.erase(t.first);
@ -432,13 +473,14 @@ void class_tag::append_super(const class_tag& tag, const std::string& path)
void class_tag::expand(class_tag& root)
{
if(!super_.empty()) {
class_tag* super_tag = root.find_tag(super_, root);
class_tag* super_tag = root.find_tag(super_, root, config());
if(super_tag) {
if(super_tag != this) {
super_tag->expand(root);
append_super(*super_tag, super_);
super_.clear();
} else {
// TODO: Detect super cycles too!
std::cerr << "the same" << super_tag->name_ << "\n";
}
}
@ -446,4 +488,98 @@ void class_tag::expand(class_tag& root)
}
}
void class_tag::add_switch(const config& switch_cfg)
{
config default_cfg;
const std::string key = switch_cfg["key"];
for(const auto& case_cfg : switch_cfg.child_range("case")) {
const std::vector<std::string> values = utils::split(case_cfg["value"]);
config filter;
for(const auto& value : values) {
filter.add_child("or")[key] = value;
default_cfg.add_child("not")[key] = value;
}
conditions_.emplace_back(case_cfg, filter);
const std::string name = formatter() << get_name() << '[' << key << '=' << case_cfg["value"] << ']';
conditions_.back().set_name(name);
}
if(switch_cfg.has_child("default")) {
conditions_.emplace_back(switch_cfg.child("default"), default_cfg);
const std::string name = formatter() << get_name() << "[default]";
conditions_.back().set_name(name);
}
}
void class_tag::add_filter(const config& cond_cfg)
{
config filter = cond_cfg;
filter.clear_children("then", "else");
if(cond_cfg.has_child("then")) {
conditions_.emplace_back(cond_cfg.child("then"), filter);
const std::string name = formatter() << get_name() << "[then]";
conditions_.back().set_name(name);
}
if(cond_cfg.has_child("else")) {
conditions_.emplace_back(cond_cfg.child("else"), config{"not", filter});
const std::string name = formatter() << get_name() << "[else]";
conditions_.back().set_name(name);
}
}
bool class_condition::matches(const config& cfg) const
{
if(cfg.empty()) {
// Conditions are not allowed to match an empty config.
// If they were, the conditions might be considered when expanding super-tags.
// That would result in a condition tag being used for the expansion, rather than
// the base tag, which would be bad.
return false;
}
return cfg.matches(filter_);
}
void class_tag::tag_iterator::init(const class_tag& base_tag)
{
current = base_tag.tags_.begin();
if(current != base_tag.tags_.end()) {
condition_queue.push(&base_tag);
}
}
void class_tag::tag_iterator::increment()
{
++current;
while(current== condition_queue.front()->tags_.end()) {
condition_queue.pop();
if(condition_queue.empty()) {
return;
}
const class_tag& new_base = *condition_queue.front();
current= new_base.tags_.begin();
push_new_tag_conditions(new_base);
}
}
void class_tag::key_iterator::init(const class_tag& base_tag)
{
current = base_tag.keys_.begin();
if(current != base_tag.keys_.end()) {
condition_queue.push(&base_tag);
}
}
void class_tag::key_iterator::increment()
{
++current;
while(current == condition_queue.front()->keys_.end()) {
condition_queue.pop();
if(condition_queue.empty()) {
return;
}
const class_tag& new_base = *condition_queue.front();
current = new_base.keys_.begin();
push_new_tag_conditions(new_base);
}
}
} // namespace schema_validation

View file

@ -26,10 +26,12 @@
#include <sstream>
#include <string>
#include <vector>
#include <queue>
#include <boost/regex.hpp>
class config;
#include <boost/iterator/iterator_facade.hpp>
#include <boost/range/iterator.hpp>
#include "config.hpp"
namespace schema_validation
{
@ -237,6 +239,8 @@ private:
bool fuzzy_;
};
class class_condition;
/**
* Stores information about tag.
* Each tags is an element of great tag tree. This tree is close to filesystem:
@ -251,6 +255,62 @@ public:
using tag_map = std::map<std::string, class_tag>;
using key_map = std::map<std::string, class_key>;
using link_map = std::map<std::string, std::string>;
using condition_list = std::vector<class_condition>;
private:
void push_new_tag_conditions(std::queue<const class_tag*> q, const class_tag& tag);
template<typename T, typename Map = std::map<std::string, T>>
class iterator : public boost::iterator_facade<iterator<T>, const typename Map::value_type, std::forward_iterator_tag>
{
std::queue<const class_tag*> condition_queue;
typename Map::const_iterator current;
const config& match;
public:
// Construct a begin iterator
iterator(const class_tag& base_tag, const config& match) : match(match)
{
init(base_tag);
push_new_tag_conditions(base_tag);
}
// Construct an end iterator
// That weird expression is to get a reference to an "invalid" config.
iterator() : match(config().child("a")) {}
private:
friend class boost::iterator_core_access;
void init(const class_tag& base_tag);
void increment();
void push_new_tag_conditions(const class_tag& tag)
{
for(const auto& condition : tag.conditions_) {
if(condition.matches(match)) {
condition_queue.push(&condition);
}
}
}
bool equal(const iterator<T, Map>& other) const
{
if(condition_queue.empty() && other.condition_queue.empty()) {
return true;
}
if(condition_queue.empty() || other.condition_queue.empty()) {
return false;
}
if(condition_queue.front() != other.condition_queue.front()) {
return false;
}
if(current != other.current) {
return false;
}
return true;
}
typename iterator<T,Map>::reference dereference() const
{
return *current;
}
};
template<typename T, typename Map> friend class iterator;
using tag_iterator = iterator<class_tag>;
using key_iterator = iterator<class_key>;
public:
class_tag()
: name_("")
@ -386,6 +446,10 @@ public:
void add_link(const std::string& link);
void add_switch(const config& switch_cfg);
void add_filter(const config& cond_cfg);
/**
* Tags are usually organized in tree.
* This function helps to add a tag to his exact place in tree
@ -408,7 +472,7 @@ public:
}
/** Returns pointer to child key. */
const class_key* find_key(const std::string& name) const;
const class_key* find_key(const std::string& name, const config& match) const;
/** Returns pointer to child link. */
const std::string* find_link(const std::string& name) const;
@ -417,19 +481,19 @@ public:
* Returns pointer to tag using full path to it.
* Also work with links
*/
const class_tag* find_tag(const std::string& fullpath, const class_tag& root) const;
const class_tag* find_tag(const std::string& fullpath, const class_tag& root, const config& match) const;
/** Calls the expansion on each child. */
void expand_all(class_tag& root);
const tag_map& tags() const
boost::iterator_range<tag_iterator> tags(const config& cfg_match) const
{
return tags_;
return {tag_iterator(*this, cfg_match), tag_iterator()};
}
const key_map& keys() const
boost::iterator_range<key_iterator> keys(const config& cfg_match) const
{
return keys_;
return {key_iterator(*this, cfg_match), key_iterator()};
}
const link_map& links() const
@ -437,6 +501,11 @@ public:
return links_;
}
const condition_list& conditions() const
{
return conditions_;
}
void remove_key_by_name(const std::string& name)
{
keys_.erase(name);
@ -473,6 +542,9 @@ private:
/** links to possible children. */
link_map links_;
/** conditional partial matches */
condition_list conditions_;
/** whether this is a "fuzzy" tag. */
bool fuzzy_;
@ -488,9 +560,9 @@ private:
*/
void printl(std::ostream& os, int level, int step = 4);
class_tag* find_tag(const std::string & fullpath, class_tag & root)
class_tag* find_tag(const std::string & fullpath, class_tag & root, const config& match)
{
return const_cast<class_tag*>(const_cast<const class_tag*>(this)->find_tag(fullpath, root));
return const_cast<class_tag*>(const_cast<const class_tag*>(this)->find_tag(fullpath, root, match));
}
void add_tags(const tag_map& list)
@ -508,10 +580,29 @@ private:
links_.insert(list.begin(), list.end());
}
void add_conditions(const condition_list& list)
{
conditions_.insert(conditions_.end(), list.begin(), list.end());
}
/** Copies tags, keys and links of tag to this. */
void append_super(const class_tag& tag, const std::string& super);
/** Expands all "super" copying their data to this. */
void expand(class_tag& root);
};
extern template class class_tag::iterator<class_tag>;
extern template class class_tag::iterator<class_key>;
/**
* Stores information about a conditional portion of a tag.
* Format is the same as class_tag.
*/
class class_condition : public class_tag {
config filter_;
public:
class_condition(const config& info, const config& filter) : class_tag(info), filter_(filter) {}
bool matches(const config& cfg) const;
};
}

View file

@ -49,6 +49,7 @@ public:
* @param file Name of file
*/
virtual void open_tag(const std::string & name,
const config& parent,
int start_line,
const std::string &file,
bool addittion = false) = 0;