diff --git a/data/schema/gui.cfg b/data/schema/gui.cfg index c338823a1e7f4..574c153c1b4d7 100644 --- a/data/schema/gui.cfg +++ b/data/schema/gui.cfg @@ -3,37 +3,49 @@ {./types/formula.cfg} [type] name=border - value="^(top|bottom|left|right|all)?(,\s*(top|bottom|left|right|all))*$" + [union] + [type] + [list] + [element] + value="top|bottom|left|right" + [/element] + [/list] + [/type] + [type] + value="all" + [/type] + [/union] [/type] [type] name=font_family - value="^(sans|monospace|light|script)?$" + value="sans|monospace|light|script|" [/type] [type] name=font_style - value="^(normal|bold|italic|underline|light)?$" + value="normal|bold|italic|underline|light|" [/type] [type] name=grow_direction - value="^horizontal|vertical$" + value="horizontal|vertical" [/type] - [type] - name=h_align - value="^left|right|center$" - [/type] - {FORMULA_TYPE h_align} [type] name=resize_mode - value="^scale|stretch|tile|tile_center$" + value="scale|stretch|tile|tile_center" [/type] [type] name=scrollbar_mode - value="^always|never|auto|initial_auto$" + value="always|never|auto|initial_auto" + [/type] + [type] + name=h_align + value="left|right|center" [/type] [type] name=v_align - value="^top|bottom|center$" + value="top|bottom|center" [/type] + {FORMULA_TYPE h_align} + {FORMULA_TYPE v_align} [tag] name="root" min="1" diff --git a/src/serialization/schema_validator.cpp b/src/serialization/schema_validator.cpp index 87432ae3cad17..f43ab28debc30 100644 --- a/src/serialization/schema_validator.cpp +++ b/src/serialization/schema_validator.cpp @@ -173,7 +173,7 @@ bool schema_validator::read_config_file(const std::string& filename) } for(const config& type : g.child_range("type")) { try { - types_[type["name"].str()] = class_type(type); + types_[type["name"].str()] = class_type::from_config(type); } catch(std::exception&) { // Need to check all type values in schema-generator } @@ -285,7 +285,7 @@ void schema_validator::validate_key( cache_.top()[&cfg].emplace_back( WRONG_TYPE, file, start_line, 0, stack_.top()->get_name(), name, key->get_type()); continue; - } else if(itt->second.matches(value, types_)) { + } else if(itt->second->matches(value, types_)) { matched = true; break; } diff --git a/src/serialization/schema_validator.hpp b/src/serialization/schema_validator.hpp index 46c65fc3caa2e..322c7c5cc468c 100644 --- a/src/serialization/schema_validator.hpp +++ b/src/serialization/schema_validator.hpp @@ -145,6 +145,6 @@ class schema_validator : public abstract_validator std::stack cache_; /** Type validators. */ - std::map types_; + class_type::map types_; }; } // namespace schema_validation{ diff --git a/src/serialization/tag.cpp b/src/serialization/tag.cpp index a7e11e097c1ab..16cb3e90e5fff 100644 --- a/src/serialization/tag.cpp +++ b/src/serialization/tag.cpp @@ -19,6 +19,7 @@ #include "serialization/tag.hpp" #include "serialization/string_utils.hpp" +#include "boost/optional.hpp" #include "config.hpp" @@ -39,93 +40,88 @@ class_tag any_tag("", 0, -1, "", true); * @end{tag}{name="key"} * @end{parent}{name="wml_schema/tag/"} */ -class_type::class_type(const config& cfg) - : name_(cfg["name"].str()) +std::shared_ptr class_type::from_config(const config& cfg) { + boost::optional composite_range; + std::shared_ptr type; if(cfg.has_child("union")) { - join = UNION; - for(const config& elem : cfg.child("union").child_range("type")) { - if(elem.has_attribute("value")) { - patterns_.emplace_back("^(?:" + elem["value"].str() + ")$"); - } else if(elem.has_attribute("link")) { - links_.emplace_back(elem["link"].str()); - } - } + type = std::make_shared(cfg["name"]); + composite_range.emplace(cfg.child("union").child_range("type")); } else if(cfg.has_child("intersection")) { - join = INTERSECTION; - for(const config& elem : cfg.child("intersection").child_range("type")) { - if(elem.has_attribute("value")) { - patterns_.emplace_back("^(?:" + elem["value"].str() + ")$"); - } else if(elem.has_attribute("link")) { - links_.emplace_back(elem["link"].str()); - } - } + type = std::make_shared(cfg["name"]); + composite_range.emplace(cfg.child("intersection").child_range("type")); } else if(cfg.has_child("list")) { const config& list_cfg = cfg.child("list"); - join = UNION; - is_list_ = true; - list_min_ = list_cfg["min"].to_int(); - list_max_ = list_cfg["max"].str() == "infinite" ? -1 : list_cfg["max"].to_int(-1); - if(list_max_ < 0) list_max_ = INT_MAX; - split_ = list_cfg["split"].str(","); - for(const config& elem : cfg.child("list").child_range("element")) { - if(elem.has_attribute("value")) { - patterns_.emplace_back("^(?:" + elem["value"].str() + ")$"); - } else if(elem.has_attribute("link")) { - links_.emplace_back(elem["link"].str()); - } - } + int list_min = list_cfg["min"].to_int(); + int list_max = list_cfg["max"].str() == "infinite" ? -1 : list_cfg["max"].to_int(-1); + if(list_max < 0) list_max = INT_MAX; + type = std::make_shared(cfg["name"], list_cfg["split"].str(","), list_min, list_max); + composite_range.emplace(list_cfg.child_range("element")); } else if(cfg.has_attribute("value")) { - patterns_.emplace_back(cfg["value"].str()); + type = std::make_shared(cfg["name"], cfg["value"]); } else if(cfg.has_attribute("link")) { - links_.emplace_back(cfg["link"].str()); + type = std::make_shared(cfg["name"], cfg["link"]); } -} - -bool class_type::matches(const std::string& value, const std::map& type_map) const { - if(is_list_ && !in_list_match_) { - if(this->name_ == "range") { - in_list_match_ = true; - } - in_list_match_ = true; - boost::sregex_token_iterator it(value.begin(), value.end(), split_, -1); - int n = !value.empty(); - bool result = std::any_of(it, boost::sregex_token_iterator(), [this, &type_map, &n](const boost::ssub_match& match){ - if(!match.matched) return false; - n++; - return this->matches(std::string(match.first, match.second), type_map); - }); - in_list_match_ = false; - return result && n >= list_min_ && n <= list_max_; - } - for(const auto& pat : patterns_) { - boost::smatch sub; - bool res = boost::regex_match(value, sub, pat); - switch(join) { - case UNION: if(res) return true; else break; - case INTERSECTION: if(!res) return false; else break; + if(composite_range) { + auto composite_type = std::dynamic_pointer_cast(type); + for(const config& elem : *composite_range) { + composite_type->add_type(class_type::from_config(elem)); } } - for(const auto& link : links_) { - auto it = type_map.find(link); + return type; +} + +bool class_type_simple::matches(const std::string& value, const map&) const +{ + boost::smatch sub; + return boost::regex_match(value, sub, pattern_); +} + +bool class_type_alias::matches(const std::string& value, const map& type_map) const +{ + if(!cached_) { + auto it = type_map.find(link_); if(it == type_map.end()) { // TODO: Error message about the invalid type? - continue; - } - bool res = it->second.matches(value, type_map); - switch(join) { - case UNION: if(res) return true; else break; - case INTERSECTION: if(!res) return false; else break; + return false; } + cached_ = it->second; } - switch(join) { - case UNION: return false; - case INTERSECTION: return true; + return cached_->matches(value, type_map); +} + +bool class_type_union::matches(const std::string& value, const map& type_map) const +{ + for(const auto& type : subtypes_) { + if(type->matches(value, type_map)) { + return true; + } } - assert(false && "class_type::matches reached end of function because join value was corrupted"); return false; } +bool class_type_intersection::matches(const std::string& value, const map& type_map) const +{ + for(const auto& type : subtypes_) { + if(!type->matches(value, type_map)) { + return false; + } + } + return true; +} + +bool class_type_list::matches(const std::string& value, const map& type_map) const +{ + boost::sregex_token_iterator it(value.begin(), value.end(), split_, -1), end; + int n = !value.empty(); + bool result = std::all_of(it, end, [this, &type_map, &n](const boost::ssub_match& match){ + if(!match.matched) return true; + n++; + return this->class_type_union::matches(std::string(match.first, match.second), type_map); + }); + return result && n >= min_ && n <= max_; +} + class_key::class_key(const config& cfg) : name_(cfg["name"].str()) , type_(cfg["type"].str()) diff --git a/src/serialization/tag.hpp b/src/serialization/tag.hpp index 8612d2eb1823e..1dc4aa801dc13 100644 --- a/src/serialization/tag.hpp +++ b/src/serialization/tag.hpp @@ -35,24 +35,65 @@ namespace schema_validation { class class_type { +protected: std::string name_; - std::vector patterns_; - std::vector links_; - boost::regex split_; - bool is_list_ = false; - int list_min_ = 0, list_max_ = -1; - mutable bool in_list_match_ = false; public: - class_type() : name_("") {} - class_type(const std::string& name, const std::string& pattern) : name_(name), patterns_(1, boost::regex(pattern)) - {} - explicit class_type(const config&); + class_type() = delete; + class_type(const std::string& name) : name_(name) {} + using map = std::map>; + virtual bool matches(const std::string& value, const map& type_map) const = 0; + static std::shared_ptr from_config(const config& cfg); +}; - bool matches(const std::string& value, const std::map& type_map) const; +class class_type_simple : public class_type { + boost::regex pattern_; +public: + class_type_simple(const std::string& name, const std::string& pattern) : class_type(name), pattern_(pattern) {} + bool matches(const std::string& value, const map& type_map) const override; +}; - enum CLASS {UNION, INTERSECTION}; -private: - CLASS join = UNION; +class class_type_alias : public class_type { + mutable std::shared_ptr cached_; + std::string link_; +public: + class_type_alias(const std::string& name, const std::string& link) : class_type(name), link_(link) {} + bool matches(const std::string& value, const map& type_map) const override; +}; + +class class_type_composite : public class_type { +protected: + std::vector> subtypes_; +public: + class_type_composite(const std::string& name) : class_type(name) {} + void add_type(std::shared_ptr type) + { + subtypes_.push_back(type); + } +}; + +class class_type_union : public class_type_composite { +public: + class_type_union(const std::string& name) : class_type_composite(name) {} + bool matches(const std::string& value, const map& type_map) const override; +}; + +class class_type_intersection : public class_type_composite { +public: + class_type_intersection(const std::string& name) : class_type_composite(name) {} + bool matches(const std::string& value, const map& type_map) const override; +}; + +class class_type_list : public class_type_union { + boost::regex split_; + int min_ = 0, max_ = -1; +public: + class_type_list(const std::string& name, const std::string& pattern, int min, int max) + : class_type_union(name) + , split_(pattern) + , min_(min) + , max_(max) + {} + bool matches(const std::string& value, const map& type_map) const override; }; /**