From 0464f5f7fc33998ebb342a1963d5bb3cf270f98a Mon Sep 17 00:00:00 2001 From: twanvl Date: Sat, 17 Mar 2007 23:58:16 +0000 Subject: [PATCH] The DECLARE_TYPEOF(()) calls don't work in MSVC, I changed it to use a COMMA macro instead of , If this doesn't work in GCC, the COMMA definition could be made only for MSVC, then GCC sees DECLARE_TYPEOF(map). GCC doesn't need DECLARE_TYPEOF anyway. Keyword expansion now works, still todo: - marking parameters, e.g. "Cycling 2W" -> "Cycling 2W" - user interface for toggling reminder text - user interface for keywords git-svn-id: svn://svn.code.sf.net/p/magicseteditor/code/trunk@210 0fc631ac-6414-0410-93d0-97cfa31319b6 --- data/en.mse-locale/locale | 19 +- data/magic.mse-game/game | 18 +- src/data/action/symbol.cpp | 6 +- src/data/card.cpp | 2 +- src/data/field/choice.cpp | 2 +- src/data/format/image_to_symbol.cpp | 7 +- src/data/keyword.cpp | 359 +++++++++++++++++++++---- src/data/keyword.hpp | 64 +++-- src/data/set.cpp | 2 +- src/data/set.hpp | 2 + src/gui/control/card_list.cpp | 4 +- src/gui/control/graph.cpp | 2 +- src/gui/control/native_look_editor.cpp | 2 +- src/gui/value/text.cpp | 2 +- src/render/card/viewer.cpp | 2 +- src/resource/msw/mse.rc | 4 +- src/script/context.cpp | 2 +- src/script/functions.cpp | 90 +++++-- src/script/image.cpp | 2 +- src/script/script_manager.cpp | 4 +- src/script/to_value.hpp | 18 +- src/script/value.cpp | 34 +-- src/script/value.hpp | 2 + src/util/for_each.hpp | 13 +- src/util/locale.hpp | 3 +- src/util/platform.hpp | 13 +- src/util/string.hpp | 3 +- src/util/tagged_string.cpp | 26 +- src/util/tagged_string.hpp | 14 + 29 files changed, 560 insertions(+), 161 deletions(-) diff --git a/data/en.mse-locale/locale b/data/en.mse-locale/locale index 8ac80d15..b9d25752 100644 --- a/data/en.mse-locale/locale +++ b/data/en.mse-locale/locale @@ -260,8 +260,11 @@ error: unable to store file: Error while saving, unable to store file # Script stuff - collection has no member: Collection has no member '%s' - object has no member: Object has no member '%s' + has no member: %s has no member '%s' + can't convert: Can't convert from %s to %s + has no member value: String "%s" has no member '%s' + can't convert value: Can't convert "%s" from %s to %s + unsupported format: Invalid string format: '%s' # Image stuff coordinates for blending overlap: Coordinates for blending overlap @@ -287,9 +290,15 @@ error: ############################################################## Types used in scripts type: - real: Real number - integer: Integer number - string: String + function: function + collection: collection + object: object + real: real number + integer: integer number + string: string + boolean: boolean + color: color + nil: nothing ############################################################## Magic game: diff --git a/data/magic.mse-game/game b/data/magic.mse-game/game index 5cce5684..66dfed6a 100644 --- a/data/magic.mse-game/game +++ b/data/magic.mse-game/game @@ -161,18 +161,22 @@ init script: # after: ")", # fix_english: true # ) + - # step 3 : expand shortcut words ~ and CARDNAME + expand_keywords_rule( + default_expand: { set.automatic_reminder_text == "yes" }, + combine: { "{keyword} ({reminder})" } + ) + + # step 3a : expand shortcut words ~ and CARDNAME replace_rule( match: "~|~THIS~|CARDNAME", in_context: "(^|[[:space:]]|\\()", # TODO: Allow any punctuation before replace: "" ) + - # step 4 : fill in atom fields + # step 3b : fill in atom fields tag_contents_rule( tag: "", contents: { if card_name=="" then "CARDNAME" else card_name } ) + - # step 4.5 : explict non mana symbols + # step 4 : explict non mana symbols replace_rule( match: "\\][STXYZWUBRG0-9/|]+\\[", replace: {"" + mana_filter_t() + ""} ) + @@ -802,7 +806,7 @@ keyword parameter type: keyword parameter type: name: cost #insert as: word - match: ***:[XYZ0-9WUBRGS/]+|[^(.,\n]|([XYZ0-9WUBRGS/]+,)?[^(.,\n]* + match: [XYZ0-9WUBRGS/]+|[^(.,\n]|([XYZ0-9WUBRGS/]+,)?[^(.,\n]* keyword parameter type: name: number match: [XYZ0-9]+ @@ -821,11 +825,11 @@ keyword parameter type: keyword parameter type: name: action #insert as: word - match: ***:[^(.,\n]+ + match: [^(.,\n]+ keyword parameter type: name: name #insert as: word - match: ***:[^(.,\n]+ + match: [^(.,\n]+ ############################# All Magic keywords # By JrEye and Neko_Asakami @@ -851,7 +855,7 @@ keyword: keyword: Cycling separator: whitespace [ ] parameter: cost - reminder: , Discard this card: Draw a card. + reminder: {param1}, Discard this card: Draw a card. keyword: keyword: Trample reminder: If this creature would deal enough combat damage to its blockers to destroy them, you may have it deal the rest of its damage to defending player. diff --git a/src/data/action/symbol.cpp b/src/data/action/symbol.cpp index 0b8c6197..53a053d9 100644 --- a/src/data/action/symbol.cpp +++ b/src/data/action/symbol.cpp @@ -9,8 +9,8 @@ #include #include -DECLARE_TYPEOF_COLLECTION((pair)); -DECLARE_TYPEOF_COLLECTION((pair)); +DECLARE_TYPEOF_COLLECTION(pair); +DECLARE_TYPEOF_COLLECTION(pair); DECLARE_TYPEOF_COLLECTION(SymbolPartP); DECLARE_TYPEOF_COLLECTION(ControlPointP); @@ -204,11 +204,11 @@ void SymbolPartScaleAction::update() { // the size after the move newMin = newRealMin; newSize = newRealSize; if (constrain && scaleX != 0 && scaleY != 0) { - // TODO : snapping Vector2D scale = newSize.div(tmpSize); scale = constrain_vector(scale, true, true); newSize = tmpSize.mul(scale); newMin += (newRealSize - newSize).mul(Vector2D(scaleX == -1 ? 1 : 0, scaleY == -1 ? 1 : 0)); + // TODO : snapping } else if (snap >= 0) { if (scaleX + scaleY < 0) { newMin = snap_vector(newMin, snap); diff --git a/src/data/card.cpp b/src/data/card.cpp index cf697086..6c01b41b 100644 --- a/src/data/card.cpp +++ b/src/data/card.cpp @@ -13,7 +13,7 @@ #include DECLARE_TYPEOF_COLLECTION(FieldP); -DECLARE_TYPEOF_NO_REV((IndexMap)); +DECLARE_TYPEOF_NO_REV(IndexMap); // ----------------------------------------------------------------------------- : Card diff --git a/src/data/field/choice.cpp b/src/data/field/choice.cpp index 9b11c2fa..eacedc77 100644 --- a/src/data/field/choice.cpp +++ b/src/data/field/choice.cpp @@ -10,7 +10,7 @@ #include DECLARE_TYPEOF_COLLECTION(ChoiceField::ChoiceP); -DECLARE_TYPEOF((map)); +DECLARE_TYPEOF(map); // ----------------------------------------------------------------------------- : ChoiceField diff --git a/src/data/format/image_to_symbol.cpp b/src/data/format/image_to_symbol.cpp index 340f5d14..d457eb36 100644 --- a/src/data/format/image_to_symbol.cpp +++ b/src/data/format/image_to_symbol.cpp @@ -9,6 +9,7 @@ #include #include #include +#include DECLARE_TYPEOF_COLLECTION(ControlPointP); DECLARE_TYPEOF_COLLECTION(SymbolPartP); @@ -99,7 +100,7 @@ bool is_mse1_symbol(const Image& img) { int r = *d++; int g = *d++; int b = *d++; - delta += fabs(r - b) + fabs(r - g) + fabs(b - g); + delta += abs(r - b) + abs(r - g) + abs(b - g); } } if (delta > 5000) return false; // not black & white enough @@ -357,7 +358,7 @@ void straighten(SymbolPart& part) { Vector2D bb = next.delta_before.normalized(); // if the area beneath the polygon formed by the handles is small // then it is a straight line - double cpDot = fabs(aa.cross(ab)) + fabs(bb.cross(ab)); + double cpDot = abs(aa.cross(ab)) + abs(bb.cross(ab)); if (cpDot < treshold) { cur.segment_after = next.segment_before = SEGMENT_LINE; cur.delta_after = next.delta_before = Vector2D(); @@ -374,7 +375,7 @@ void merge_lines(SymbolPart& part) { Vector2D a = part.getPoint(i-1)->pos, b = cur.pos, c = part.getPoint(i+1)->pos; Vector2D ab = (a-b).normalized(); Vector2D bc = (b-c).normalized(); - double angle_len = fabs( atan2(ab.x,ab.y) - atan2(bc.x,bc.y)) * (a-c).lengthSqr(); + double angle_len = abs( atan2(ab.x,ab.y) - atan2(bc.x,bc.y)) * (a-c).lengthSqr(); bool keep = angle_len >= .0001; if (!keep) { part.points.erase(part.points.begin() + i); diff --git a/src/data/keyword.cpp b/src/data/keyword.cpp index ef636097..5c0f5ea7 100644 --- a/src/data/keyword.cpp +++ b/src/data/keyword.cpp @@ -10,7 +10,12 @@ #include class KeywordTrie; -DECLARE_TYPEOF((map)); +DECLARE_TYPEOF(map); +DECLARE_TYPEOF_COLLECTION(KeywordTrie*); +DECLARE_TYPEOF_COLLECTION(KeywordP); +DECLARE_TYPEOF_COLLECTION(KeywordParamP); +DECLARE_TYPEOF_COLLECTION(KeywordExpansionP); +DECLARE_TYPEOF_COLLECTION(const KeywordExpansion*); // ----------------------------------------------------------------------------- : Reflection @@ -58,10 +63,91 @@ IMPLEMENT_REFLECTION(Keyword) { read_compat(tag, this); REFLECT(expansions); REFLECT(rules); - REFLECT(mode); +// REFLECT(mode); } +// ----------------------------------------------------------------------------- : Regex stuff + +/// Make sure the given regex does no capturing +/** Basicly replaces "(" with "(?:" */ +String make_non_capturing(const String& re) { + String ret; + bool escape = false, bracket = false, capture = false; + FOR_EACH_CONST(c, re) { + if (capture && c != _('?')) { + // change this capture into a non-capturing "(" by appending "?:" + ret += _("?:"); + capture = false; + } + if (escape) { // second char of escape sequence + escape = false; + } else if (c == _('\\')) { // start of escape sequence + escape = true; + } else if (c == _('[')) { // start of [...] + bracket = true; + } else if (c == _(']')) { // end of [...] + bracket = false; + } else if (bracket && c == _('(')) { + // wx has a bug, it counts the '(' in "[(]" as a matching group + // escape it so wx doesn't see it + ret += _('\\'); + } else if (c == _('(')) { // start of capture? + capture = true; + } + ret += c; + } + return ret; +} + +/// Escape a single character for use in regular expressions +String regex_escape(Char c) { + if (c == _('(') || c == _(')') || c == _('[') || c == _(']') || c == _('{') || + c == _('.') || c == _('^') || c == _('$') || c == _('#') || c == _('\\') || + c == _('|') || c == _('+') || c == _('*') || c == _('?')) { + // c needs to be escaped + return _("\\") + String(1,c); + } else { + return String(1,c); + } +} + +void KeywordExpansion::prepare(const vector& param_types, bool force) { + if (!force && matchRe.IsValid()) return; + parameters.clear(); + // Prepare regex +// String regex; + vector::const_iterator param = parameters.begin(); + // Parse the 'match' string + for (size_t i = 0 ; i < match.size() ;) { + Char c = match.GetChar(i); + if (is_substr(match, i, _("name == type) { + p = pt.get(); + break; + } + } + if (!p) { + throw InternalError(_("Unknown keyword parameter type: ") + type); + } + // modify regex + regex += _("(") + make_non_capturing(p->match) + _(")"); + i = skip_tag(match, end); + } else { + regex += regex_escape(c); + i++; + } + } + if (!matchRe.Compile(regex, wxRE_ADVANCED)) { + throw InternalError(_("Error creating match regex")); + } +} // ----------------------------------------------------------------------------- : KeywordTrie @@ -71,10 +157,13 @@ class KeywordTrie { KeywordTrie(); ~KeywordTrie(); - map children; ///< children after a given character (owned) - KeywordTrie* on_any_star; ///< children on /.*/ (owned) - Keyword* finished; ///< keywords that end in this node + map children; ///< children after a given character (owned) + KeywordTrie* on_any_star; ///< children on /.*/ (owned or this) + vector finished; ///< keywords expansions that end in this node + /// Insert nodes representing the given character + /** return the node where the evaluation will be after matching the character */ + KeywordTrie* insert(Char match); /// Insert nodes representing the given string /** return the node where the evaluation will be after matching the string */ KeywordTrie* insert(const String& match); @@ -94,73 +183,245 @@ KeywordTrie::~KeywordTrie() { FOR_EACH(c, children) { delete c.second; } - delete on_any_star; + if (on_any_star != this) delete on_any_star; } +KeywordTrie* KeywordTrie::insert(Char c) { + KeywordTrie*& child = children[c]; + if (!child) child = new KeywordTrie; + return child; +} KeywordTrie* KeywordTrie::insert(const String& match) { KeywordTrie* cur = this; FOR_EACH_CONST(c, match) { - KeywordTrie*& child = cur->children[c]; - if (!child) child = new KeywordTrie; - cur = child; + cur = cur->insert(c); } return cur; } KeywordTrie* KeywordTrie::insertAnyStar() { if (!on_any_star) on_any_star = new KeywordTrie; + on_any_star->on_any_star = on_any_star; // circular reference to itself return on_any_star; } -// ----------------------------------------------------------------------------- : KeywordMatcher - -/// State of the matching algorithm -class KeywordMatcher { - public: - KeywordMatcher(const String& s); - private: - String str; - size_t pos; -}; // ----------------------------------------------------------------------------- : KeywordDatabase -/// A database of keywords to allow for fast matching -/** NOTE: keywords may not be altered after they are added to the database, - * The database should be rebuild. - */ -class KeywordDatabase { - public: - /// Add a keyword to be matched - void addKeyword(const Keyword&); - - /// Find the first matching keyword, return its position - size_t firstMatch(const String& input, Keyword* keyword); - - private: - KeywordTrie root; -}; +KeywordDatabase::KeywordDatabase() + : root(nullptr) +{} -void KeywordDatabase::addKeyword(const Keyword& kw) { - // TODO +KeywordDatabase::~KeywordDatabase() { + clear(); } -// ----------------------------------------------------------------------------- : Using keywords - -KeywordDatabaseP new_keyword_database() { - return new_shared(); -} -void add_keyword(KeywordDatabase& db, const Keyword& kw) { - db.addKeyword(kw); +void KeywordDatabase::clear() { + delete root; + root = nullptr; } +void KeywordDatabase::add(const vector& kws) { + FOR_EACH_CONST(kw, kws) { + add(*kw); + } +} +void KeywordDatabase::add(const Keyword& kw) { + FOR_EACH_CONST(e, kw.expansions) { + add(*e); + } +} -String expand_keywords(const KeywordDatabase& db, const String& text) { - // 1. Remove all old reminder texts - String s = remove_tag_contents(text, _("")); - // 2. Process keywords +void KeywordDatabase::add(const KeywordExpansion& e) { + if (!root) { + root = new KeywordTrie; + root->on_any_star = root; + } + KeywordTrie* cur = root->insertAnyStar(); + for (size_t i = 0 ; i < e.match.size() ;) { + Char c = e.match.GetChar(i); + if (is_substr(e.match, i, _("insertAnyStar(); + i = match_close_tag_end(e.match, i); + } else { + cur = cur->insert(c); + i++; + } + } + // now cur is the trie after matching the keyword anywhere in the input text + cur->finished.push_back(&e); +} + +void KeywordDatabase::prepare_parameters(const vector& ps, const vector& kws) { + FOR_EACH_CONST(kw, kws) { + prepare_parameters(ps, *kw); + } +} +void KeywordDatabase::prepare_parameters(const vector& ps, const Keyword& kw) { + FOR_EACH_CONST(e, kw.expansions) { + e->prepare(ps); + } +} + +// ----------------------------------------------------------------------------- : KeywordDatabase : matching + +// transitive closure of a state, follow all on_any_star links +void closure(vector& state) { + for (size_t j = 0 ; j < state.size() ; ++j) { + if (state[j]->on_any_star && state[j]->on_any_star != state[j]) { + state.push_back(state[j]->on_any_star); + } + } +} + +//DEBUG +void dump(int i, KeywordTrie* t) { + FOR_EACH(c, t->children) { + wxLogDebug(String(i,_(' ')) + c.first + _(" ") + String::Format(_("%p"),c.second)); + dump(i+2, c.second); + } + if (t->on_any_star) { + wxLogDebug(String(i,_(' ')) + _(".*") + _(" ") + String::Format(_("%p"),t->on_any_star)); + if (t->on_any_star != t) dump(i+2, t->on_any_star); + } +} + +String KeywordDatabase::expand(const String& text, + const ScriptValueP& expand_default, + const ScriptValueP& combine_script, + Context& ctx) const { + // DEBUG: dump db + //dump(0, root); - // TODO + assert(combine_script); - return s; + // Remove all old reminder texts + String s = remove_tag_contents(text, _("")); + s = remove_tag_contents(s, _("")); // OLD, TODO: REMOVEME + s = remove_tag(s, _(" current; // current location(s) in the trie + vector next; // location(s) after this step + set used; // keywords already investigated + current.push_back(root); + closure(current); + char expand_type = 'a'; // is the keyword expanded? From tag + // Possible values are: + // - '0' = reminder text explicitly hidden + // - '1' = reminder text explicitly shown + // - 'a' = reminder text in default state, hidden + // - 'A' = reminder text in default state, shown + + for (size_t i = 0 ; i < s.size() ;) { + Char c = s.GetChar(i); + // tag? + if (c == _('<')) { + if (is_substr(s, i, _(" + s = s.erase(i, skip_tag(s,i)-i); // remove the tag from the string + } else if (is_substr(s, i, _("on_any_star) { + next.push_back(kt->on_any_star); + } + map::const_iterator it = kt->children.find(c); + if (it != kt->children.end()) { + next.push_back(it->second); + wxLogDebug(c + String(_(" -> ")) + String::Format(_("%p"), it->second)); + } + } + // next becomes current + swap(current, next); + next.clear(); + closure(current); + // are we done? + FOR_EACH(n, current) { + FOR_EACH(f, n->finished) { + const KeywordExpansion* kw = f; + if (used.insert(kw).second) { + // we have found a possible match, which we have not seen before + assert(kw->matchRe.IsValid()); + + // try to match it against the *untagged* string + if (kw->matchRe.Matches(untagged)) { + // Everything before the keyword + size_t start_u, len_u; + kw->matchRe.GetMatch(&start_u, &len_u, 0); + size_t start = untagged_to_index(s, start_u, true), + end = untagged_to_index(s, start_u + len_u, true); + result += s.substr(0, start); + + // Set parameters in context + size_t match_count = kw->matchRe.GetMatchCount(); + for (size_t j = 1 ; j < match_count ; ++j) { + size_t start_u, len_u; + kw->matchRe.GetMatch(&start_u, &len_u, j); + String param = untagged.substr(start_u, len_u); + if (j-1 < kw->parameters.size() && kw->parameters[j-1]->script) { + // apply parameter script + param = kw->parameters[j-1]->script.invoke(ctx)->toString(); + } + ctx.setVariable(String(_("param")) << (int)j, toScript(param)); + } + ctx.setVariable(_("mode"), toScript(kw->mode)); + + // Show reminder text? + bool expand = expand_type == _('1'); + if (!expand && expand_type != _('0')) { + // default expand, determined by script + expand = expand_default && (bool)*expand_default->eval(ctx); + expand_type = expand ? _('A') : _('a'); + } + + // Combine keyword & reminder with result + if (expand) { + String reminder = kw->reminder.invoke(ctx)->toString(); + ctx.setVariable(_("keyword"), toScript(s.substr(start, end - start))); + ctx.setVariable(_("reminder"), toScript(reminder)); + result += _(""); + result += combine_script->eval(ctx)->toString(); + result += _(""); + } else { + result += _(""); + result += s.substr(start, end - start); + result += _(""); + } + + // After keyword + s = s.substr(end); + untagged = untagged.substr(start_u + len_u); + used.clear(); + expand_type = _('a'); + goto matched_keyword; + } + + } + } + } + } + // Remainder of the string + result += s; s.clear(); + + matched_keyword:; + } + + return result; } diff --git a/src/data/keyword.hpp b/src/data/keyword.hpp index 946f4bfe..aa5fd187 100644 --- a/src/data/keyword.hpp +++ b/src/data/keyword.hpp @@ -16,6 +16,8 @@ DECLARE_POINTER_TYPE(KeywordParam); DECLARE_POINTER_TYPE(KeywordExpansion); DECLARE_POINTER_TYPE(KeywordMode); +DECLARE_POINTER_TYPE(Keyword); +class KeywordTrie; // ----------------------------------------------------------------------------- : Keyword components @@ -24,8 +26,7 @@ class KeywordParam { public: String name; ///< Name of the parameter type String description; ///< Description of the type - String match; ///< Uncompiled regex - wxRegEx matchRe; ///< Regular expression to match + String match; ///< Regular expression to match OptionalScript script; ///< Transformation of the value for showing in the reminder text String example; ///< Example for preview dialog @@ -49,9 +50,18 @@ class KeywordExpansion { public: String match; ///< String to match, tags are used for parameters vector parameters; ///< The types of parameters -// wxRegEx splitter; ///< Regular expression to split/match the components, automatically generated + wxRegEx matchRe; ///< Regular expression to match and split parameters, automatically generated StringScript reminder; ///< Reminder text of the keyword - String mode; ///< Mode of use, can be used by scripts (only gives the name). Default is the mode of the Keyword. + String mode; ///< Mode of use, can be used by scripts (only gives the name) +// . Default is the mode of the Keyword. + String regex;//TODO REMOVE + + /// Prepare the expansion: (re)generate matchRe and the list of parameters. + /** Throws when there is an error in the input + * @param param_types A list of all parameter types. + * @param force Re-prepare even if the regex¶meters are okay + */ + void prepare(const vector& param_types, bool force = false); DECLARE_REFLECTION(); }; @@ -64,7 +74,7 @@ class Keyword { String keyword; ///< The keyword vector expansions; ///< Expansions, i.e. ways to use this keyword String rules; ///< Rules/explanation - String mode; ///< Mode of use, can be used by scripts (only gives the name) +// String mode; ///< Mode of use, can be used by scripts (only gives the name) DECLARE_REFLECTION(); }; @@ -72,21 +82,41 @@ class Keyword { // ----------------------------------------------------------------------------- : Using keywords -/// A class that allows for fast matching of keywords -class KeywordDatabase; -DECLARE_POINTER_TYPE(KeywordDatabase); - -/// Create a new keyword database -KeywordDatabaseP new_keyword_database(); - -/// Add a keyword to a KeywordDatabase +/// A database of keywords to allow for fast matching /** NOTE: keywords may not be altered after they are added to the database, * The database should be rebuild. */ -void add_keyword(KeywordDatabase& db, const Keyword& kw); - -/// Expand/update all keywords in the given string -String expand_keywords(const KeywordDatabase& db, const String& text); +class KeywordDatabase { + public: + KeywordDatabase(); + ~KeywordDatabase(); + + /// Add a list of keywords to be matched + void add(const vector&); + /// Add a keyword to be matched + void add(const Keyword&); + /// Add an expansion of a keyword to be matched + void add(const KeywordExpansion&); + + /// Prepare the parameters and match regex for a list of keywords + static void prepare_parameters(const vector&, const vector&); + static void prepare_parameters(const vector&, const Keyword&); + + /// Clear the database + void clear(); + /// Is the database empty? + inline bool empty() const { return !root; } + + /// Expand/update all keywords in the given string. + /** @param expand_default script function indicating whether reminder text should be shown by default + * @param combine_script script function to combine keyword and reminder text in some way + * @param ctx context for evaluation of scripts + */ + String expand(const String& text, const ScriptValueP& expand_default, const ScriptValueP& combine_script, Context& ctx) const; + + private: + KeywordTrie* root; ///< Data structure for finding keywords +}; // ----------------------------------------------------------------------------- : EOF #endif diff --git a/src/data/set.cpp b/src/data/set.cpp index 6183cdcd..5437a99c 100644 --- a/src/data/set.cpp +++ b/src/data/set.cpp @@ -19,7 +19,7 @@ #include DECLARE_TYPEOF_COLLECTION(CardP); -DECLARE_TYPEOF_NO_REV((IndexMap)); +DECLARE_TYPEOF_NO_REV(IndexMap); // ----------------------------------------------------------------------------- : Set diff --git a/src/data/set.hpp b/src/data/set.hpp index 336bf834..4009a9da 100644 --- a/src/data/set.hpp +++ b/src/data/set.hpp @@ -14,6 +14,7 @@ #include #include #include // for Set::value +#include #include DECLARE_POINTER_TYPE(Card); @@ -57,6 +58,7 @@ class Set : public Packaged { vector keywords; ///< Additional keywords used in this set String apprentice_code; ///< Code to use for apprentice (Magic only) ActionStack actions; ///< Actions performed on this set and the cards in it + KeywordDatabase keyword_db; ///< Database for matching keywords, must be cleared when keywords change /// A context for performing scripts /** Should only be used from the main thread! */ diff --git a/src/gui/control/card_list.cpp b/src/gui/control/card_list.cpp index 177e76f6..2a667963 100644 --- a/src/gui/control/card_list.cpp +++ b/src/gui/control/card_list.cpp @@ -26,8 +26,8 @@ DECLARE_TYPEOF_COLLECTION(CardP); DECLARE_TYPEOF_COLLECTION(FieldP); DECLARE_POINTER_TYPE(ChoiceValue); -DECLARE_TYPEOF((map)); -DECLARE_TYPEOF_NO_REV((IndexMap)); +DECLARE_TYPEOF(map); +DECLARE_TYPEOF_NO_REV(IndexMap); // ----------------------------------------------------------------------------- : Events diff --git a/src/gui/control/graph.cpp b/src/gui/control/graph.cpp index 1bdaadd3..8ef0bea1 100644 --- a/src/gui/control/graph.cpp +++ b/src/gui/control/graph.cpp @@ -15,7 +15,7 @@ DECLARE_TYPEOF_COLLECTION(GraphAxisP); DECLARE_TYPEOF_COLLECTION(GraphElementP); DECLARE_TYPEOF_COLLECTION(GraphGroup); DECLARE_TYPEOF_COLLECTION(int); -DECLARE_TYPEOF((map)); +DECLARE_TYPEOF(map); // ----------------------------------------------------------------------------- : Events diff --git a/src/gui/control/native_look_editor.cpp b/src/gui/control/native_look_editor.cpp index 09689f0a..891a2604 100644 --- a/src/gui/control/native_look_editor.cpp +++ b/src/gui/control/native_look_editor.cpp @@ -12,7 +12,7 @@ #include DECLARE_TYPEOF_COLLECTION(ValueViewerP); -DECLARE_TYPEOF_NO_REV((IndexMap)); +DECLARE_TYPEOF_NO_REV(IndexMap); // ----------------------------------------------------------------------------- : NativeLookEditor diff --git a/src/gui/value/text.cpp b/src/gui/value/text.cpp index 07f03f56..3ea7f7c8 100644 --- a/src/gui/value/text.cpp +++ b/src/gui/value/text.cpp @@ -570,7 +570,7 @@ void TextValueEditor::fixSelection(IndexType t, Movement dir) { // start and end must be on the same side of separators size_t seppos = val.find(_(" seppos) { // not on same side, move selection end before sep selection_end = index_to_cursor(val, seppos, dir); diff --git a/src/render/card/viewer.cpp b/src/render/card/viewer.cpp index 6e346fc8..5743da36 100644 --- a/src/render/card/viewer.cpp +++ b/src/render/card/viewer.cpp @@ -18,7 +18,7 @@ #include // clearDC DECLARE_TYPEOF_COLLECTION(ValueViewerP); -DECLARE_TYPEOF_NO_REV((IndexMap)); +DECLARE_TYPEOF_NO_REV(IndexMap); // ----------------------------------------------------------------------------- : DataViewer diff --git a/src/resource/msw/mse.rc b/src/resource/msw/mse.rc index 25790471..4470fffd 100644 --- a/src/resource/msw/mse.rc +++ b/src/resource/msw/mse.rc @@ -110,7 +110,7 @@ bool_no IMAGE "../common/bool_no.png" //help_page BITMAP "help_page.png" about IMAGE "../common/about.png" -two_beta IMAGE "../common/two_beta.png" +two_beta IMAGE "../common/two_beta.png" btn_normal IMAGE "../common/btn_normal.png" btn_hover IMAGE "../common/btn_hover.png" btn_focus IMAGE "../common/btn_focus.png" @@ -155,7 +155,7 @@ FILETYPE VFT_APP VALUE "License", "GNU General Public License 2 or later; This is free software, and you are welcome to redistribute it under certain conditions; See the help file for details" VALUE "FileDescription", "Magic Set Editor" VALUE "InternalName", "mse2/8" - VALUE "LegalCopyright", " 2001-2006 Twan van Laarhoven" + VALUE "LegalCopyright", "© 2001-2006 Twan van Laarhoven" VALUE "ProductName", "Magic Set Editor" } } diff --git a/src/script/context.cpp b/src/script/context.cpp index 3c98d3f4..8631dec2 100644 --- a/src/script/context.cpp +++ b/src/script/context.cpp @@ -215,7 +215,7 @@ void instrUnary (UnaryInstructionType i, ScriptValueP& a) { a = toScript(-(int)*a); break; case I_NOT: - a = toScript(!(int)*a); + a = toScript(!(bool)*a); break; } } diff --git a/src/script/functions.cpp b/src/script/functions.cpp index 1a592a6b..7d2e7a26 100644 --- a/src/script/functions.cpp +++ b/src/script/functions.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -265,7 +266,7 @@ String spec_sort(const String& spec, const String& input) { ScriptValueP ScriptRule_##funname::eval(Context& ctx) const // Utility for defining a script rule with two parameters -#define SCRIPT_RULE_2(funname, type1, name1, type2, name2) \ +#define SCRIPT_RULE_2_N(funname, type1, str1, name1, type2, str2, name2) \ class ScriptRule_##funname: public ScriptValue { \ public: \ inline ScriptRule_##funname(const type1& name1, const type2& name2) \ @@ -278,16 +279,18 @@ String spec_sort(const String& spec, const String& input) { type2 name2; \ }; \ SCRIPT_FUNCTION(funname##_rule) { \ - SCRIPT_PARAM(type1, name1); \ - SCRIPT_PARAM(type2, name2); \ + SCRIPT_PARAM_N(type1, str1, name1); \ + SCRIPT_PARAM_N(type2, str2, name2); \ return new_intrusive2(name1, name2); \ } \ SCRIPT_FUNCTION(funname) { \ - SCRIPT_PARAM(type1, name1); \ - SCRIPT_PARAM(type2, name2); \ + SCRIPT_PARAM_N(type1, str1, name1); \ + SCRIPT_PARAM_N(type2, str2, name2); \ return ScriptRule_##funname(name1, name2).eval(ctx); \ } \ ScriptValueP ScriptRule_##funname::eval(Context& ctx) const +#define SCRIPT_RULE_2(funname, type1, name1, type2, name2) \ + SCRIPT_RULE_2_N(funname, type1, _(#name1), name1, type2, _(#name2), name2) // Create a rule for spec_sorting strings @@ -339,10 +342,21 @@ SCRIPT_FUNCTION(contains) { SCRIPT_RETURN(input.find(match) != String::npos); } -SCRIPT_FUNCTION(format) { - SCRIPT_PARAM(String, format); - SCRIPT_PARAM(String, input); - SCRIPT_RETURN(format_string(_("%") + replace_all(format, _("%"), _("")), input)); +SCRIPT_RULE_1(format, String, format) { + String fmt = _("%") + replace_all(format, _("%"), _("")); + // determine type expected by format string + if (format.find_first_of(_("DdIiOoXx")) != String.npos) { + SCRIPT_PARAM(int, input); + SCRIPT_RETURN(String::Format(fmt, input)); + } else if (format.find_first_of(_("EeFfGg")) != String.npos) { + SCRIPT_PARAM(double, input); + SCRIPT_RETURN(String::Format(fmt, input)); + } else if (format.find_first_of(_("Ss")) != String.npos) { + SCRIPT_PARAM(String, input); + SCRIPT_RETURN(format_string(fmt, input)); + } else { + throw ScriptError(_ERROR_1_("unsupported format", format)); + } } // ----------------------------------------------------------------------------- : Tagged stuff @@ -381,7 +395,23 @@ SCRIPT_RULE_1(tag_remove, String, tag) { SCRIPT_RETURN(remove_tag(input, tag)); } -// ----------------------------------------------------------------------------- : Vector stuff +// ----------------------------------------------------------------------------- : Keywords + +SCRIPT_RULE_2_N(expand_keywords, ScriptValueP, _("default expand"), default_expand, + ScriptValueP, _("combine"), combine) { + SCRIPT_PARAM(String, input); + SCRIPT_PARAM(Set*, set); + KeywordDatabase& db = set->keyword_db; + if (db.empty()) { + db.add(set->game->keywords); + db.add(set->keywords); + db.prepare_parameters(set->game->keyword_parameter_types, set->game->keywords); + db.prepare_parameters(set->game->keyword_parameter_types, set->keywords); + } + SCRIPT_RETURN(db.expand(input, default_expand, combine, ctx)); +} + +// ----------------------------------------------------------------------------- : Collection stuff /// compare script values for equallity bool equal(const ScriptValue& a, const ScriptValue& b) { @@ -497,7 +527,7 @@ SCRIPT_FUNCTION_DEP(combined_editor) { size_t pos = value.find(_("type() == SCRIPT_INT) { - return (int)*value; // boolean up-to-dateness from parameter + return (bool)*value; // boolean up-to-dateness from parameter } else { return true; } diff --git a/src/script/script_manager.cpp b/src/script/script_manager.cpp index a0b64d79..60132965 100644 --- a/src/script/script_manager.cpp +++ b/src/script/script_manager.cpp @@ -22,8 +22,8 @@ DECLARE_TYPEOF(Contexts); DECLARE_TYPEOF_COLLECTION(CardP); DECLARE_TYPEOF_COLLECTION(FieldP); DECLARE_TYPEOF_COLLECTION(Dependency); -DECLARE_TYPEOF_NO_REV((IndexMap)); -DECLARE_TYPEOF_NO_REV((IndexMap)); +DECLARE_TYPEOF_NO_REV(IndexMap); +DECLARE_TYPEOF_NO_REV(IndexMap); // initialize functions, from functions.cpp void init_script_functions(Context& ctx); diff --git a/src/script/to_value.hpp b/src/script/to_value.hpp index c23a496e..636f7b5a 100644 --- a/src/script/to_value.hpp +++ b/src/script/to_value.hpp @@ -53,13 +53,13 @@ class ScriptCollection : public ScriptValue { public: inline ScriptCollection(const Collection* v) : value(v) {} virtual ScriptType type() const { return SCRIPT_COLLECTION; } - virtual String typeName() const { return _("collection"); } + virtual String typeName() const { return _TYPE_("collection"); } virtual ScriptValueP getMember(const String& name) const { long index; if (name.ToLong(&index) && index >= 0 && (size_t)index < value->size()) { return toScript(value->at(index)); } else { - throw ScriptError(_("Collection has no member ") + name); + return ScriptValue::getMember(name); } } virtual ScriptValueP makeIterator() const { @@ -79,7 +79,7 @@ ScriptValueP get_member(const map& m, const String& name) { if (it != m.end()) { return toScript(it->second); } else { - throw ScriptError(_ERROR_1_("collection has no member", name)); + throw ScriptError(_ERROR_2_("has no member", _TYPE_("collection"), name)); } } @@ -89,7 +89,7 @@ ScriptValueP get_member(const IndexMap& m, const String& name) { if (it != m.end()) { return toScript(*it); } else { - throw ScriptError(_ERROR_1_("collection has no member", name)); + throw ScriptError(_ERROR_2_("has no member", _TYPE_("collection"), name)); } } @@ -99,7 +99,7 @@ class ScriptMap : public ScriptValue { public: inline ScriptMap(const Collection* v) : value(v) {} virtual ScriptType type() const { return SCRIPT_COLLECTION; } - virtual String typeName() const { return _("collection"); } + virtual String typeName() const { return _TYPE_("collection"); } virtual ScriptValueP getMember(const String& name) const { return get_member(*value, name); } @@ -132,7 +132,7 @@ class ScriptObject : public ScriptValue { public: inline ScriptObject(const T& v) : value(v) {} virtual ScriptType type() const { return SCRIPT_OBJECT; } - virtual String typeName() const { return _("object"); } + virtual String typeName() const { return _TYPE_("object"); } virtual operator String() const { ScriptValueP d = getDefault(); return d ? *d : ScriptValue::operator String(); } virtual operator double() const { ScriptValueP d = getDefault(); return d ? *d : ScriptValue::operator double(); } virtual operator int() const { ScriptValueP d = getDefault(); return d ? *d : ScriptValue::operator int(); } @@ -147,7 +147,7 @@ class ScriptObject : public ScriptValue { if (d) { return d->getMember(name); } else { - throw ScriptError(_ERROR_1_("object has no member", name)); + throw ScriptValue::getMember(name); } } } @@ -239,7 +239,9 @@ inline ScriptValueP toScript(const Defaultable& v) { return toScript(v()); } * Throws an error if the parameter is not found. */ #define SCRIPT_PARAM(Type, name) \ - Type name = getParam(ctx.getVariable(_(#name))) + SCRIPT_PARAM_N(Type, _(#name), name) +#define SCRIPT_PARAM_N(Type, str, name) \ + Type name = getParam(ctx.getVariable(str)) template inline T getParam (const ScriptValueP& value) { diff --git a/src/script/value.cpp b/src/script/value.cpp index 7396aff1..7e8a079f 100644 --- a/src/script/value.cpp +++ b/src/script/value.cpp @@ -15,14 +15,14 @@ // Base cases ScriptValue::operator String() const { return _("[[") + typeName() + _("]]"); } -ScriptValue::operator int() const { throw ScriptError( _("Can't convert from ")+typeName()+_(" to integer number")); } -ScriptValue::operator double() const { throw ScriptError( _("Can't convert from ")+typeName()+_(" to real number" )); } -ScriptValue::operator Color() const { throw ScriptError( _("Can't convert from ")+typeName()+_(" to color" )); } -ScriptValueP ScriptValue::eval(Context&) const { throw ScriptError( _("Can't convert from ")+typeName()+_(" to function" )); } -ScriptValueP ScriptValue::getMember(const String& name) const { throw ScriptError(typeName() + _(" has no member '") + name + _("'")); } +ScriptValue::operator int() const { throw ScriptError(_ERROR_2_("can't convert", typeName(), _TYPE_("integer" ))); } +ScriptValue::operator double() const { throw ScriptError(_ERROR_2_("can't convert", typeName(), _TYPE_("real" ))); } +ScriptValue::operator Color() const { throw ScriptError(_ERROR_2_("can't convert", typeName(), _TYPE_("color" ))); } +ScriptValueP ScriptValue::eval(Context&) const { throw ScriptError(_ERROR_2_("can't convert", typeName(), _TYPE_("function"))); } +ScriptValueP ScriptValue::getMember(const String& name) const { throw ScriptError(_ERROR_2_("has no member", typeName(), name)); } ScriptValueP ScriptValue::next() { throw InternalError(_("Can't convert from ")+typeName()+_(" to iterator")); } -ScriptValueP ScriptValue::makeIterator() const { throw ScriptError( _("Can't convert from ")+typeName()+_(" to collection")); } -int ScriptValue::itemCount() const { throw ScriptError( _("Can't convert from ")+typeName()+_(" to collection")); } +ScriptValueP ScriptValue::makeIterator() const { throw ScriptError(_ERROR_2_("can't convert", typeName(), _TYPE_("collection"))); } +int ScriptValue::itemCount() const { throw ScriptError(_ERROR_2_("can't convert", typeName(), _TYPE_("collection"))); } ScriptValueP ScriptValue::dependencyMember(const String& name, const Dependency&) const { return dependency_dummy; } ScriptValueP ScriptValue::dependencies(Context&, const Dependency&) const { return dependency_dummy; } @@ -63,7 +63,7 @@ class ScriptInt : public ScriptValue { public: ScriptInt(int v) : value(v) {} virtual ScriptType type() const { return SCRIPT_INT; } - virtual String typeName() const { return _("integer number"); } + virtual String typeName() const { return _TYPE_("integer"); } virtual operator String() const { return String() << value; } virtual operator double() const { return value; } virtual operator int() const { return value; } @@ -108,7 +108,7 @@ class ScriptBool : public ScriptValue { public: ScriptBool(bool v) : value(v) {} virtual ScriptType type() const { return SCRIPT_INT; } - virtual String typeName() const { return _("boolean"); } + virtual String typeName() const { return _TYPE_("boolean"); } virtual operator String() const { return value ? _("true") : _("false"); } virtual operator int() const { return value; } private: @@ -130,7 +130,7 @@ class ScriptDouble : public ScriptValue { public: ScriptDouble(double v) : value(v) {} virtual ScriptType type() const { return SCRIPT_DOUBLE; } - virtual String typeName() const { return _("real number"); } + virtual String typeName() const { return _TYPE_("real"); } virtual operator String() const { return String() << value; } virtual operator double() const { return value; } virtual operator int() const { return (int)value; } @@ -149,14 +149,14 @@ class ScriptString : public ScriptValue { public: ScriptString(const String& v) : value(v) {} virtual ScriptType type() const { return SCRIPT_STRING; } - virtual String typeName() const { return _("string"); } + virtual String typeName() const { return _TYPE_("string"); } virtual operator String() const { return value; } virtual operator double() const { double d; if (value.ToDouble(&d)) { return d; } else { - throw ScriptError(_("Not a number: '") + value + _("'")); + throw ScriptError(_ERROR_3_("can't convert value", value, typeName(), _TYPE_("double"))); } } virtual operator int() const { @@ -168,7 +168,7 @@ class ScriptString : public ScriptValue { } else if (value == _("no") || value == _("false") || value.empty()) { return false; } else { - throw ScriptError(_("Not a number: '") + value + _("'")); + throw ScriptError(_ERROR_3_("can't convert value", value, typeName(), _TYPE_("integer"))); } } virtual operator Color() const { @@ -176,7 +176,7 @@ class ScriptString : public ScriptValue { if (wxSscanf(value.c_str(),_("rgb(%u,%u,%u)"),&r,&g,&b)) { return Color(r, g, b); } else { - throw ScriptError(_("Not a color: '") + value + _("'")); + throw ScriptError(_ERROR_3_("can't convert value", value, typeName(), _TYPE_("color"))); } } virtual int itemCount() const { return (int)value.size(); } @@ -186,7 +186,7 @@ class ScriptString : public ScriptValue { if (name.ToLong(&index) && index >= 0 && (size_t)index < value.size()) { return toScript(String(1,value[index])); } else { - throw ScriptError(_("String \"") + value + _("\" has no member ") + name); + throw ScriptError(_ERROR_2_("has no member value", value, name)); } } private: @@ -205,7 +205,7 @@ class ScriptColor : public ScriptValue { public: ScriptColor(const Color& v) : value(v) {} virtual ScriptType type() const { return SCRIPT_COLOR; } - virtual String typeName() const { return _("color"); } + virtual String typeName() const { return _TYPE_("color"); } virtual operator Color() const { return value; } virtual operator String() const { return String::Format(_("rgb(%u,%u,%u)"), value.Red(), value.Green(), value.Blue()); @@ -225,7 +225,7 @@ ScriptValueP toScript(const Color& v) { class ScriptNil : public ScriptValue { public: virtual ScriptType type() const { return SCRIPT_NIL; } - virtual String typeName() const { return _("nil"); } + virtual String typeName() const { return _TYPE_("nil"); } virtual operator String() const { return wxEmptyString; } virtual operator double() const { return 0.0; } virtual operator int() const { return 0; } diff --git a/src/script/value.hpp b/src/script/value.hpp index 03ffa38e..52977d6b 100644 --- a/src/script/value.hpp +++ b/src/script/value.hpp @@ -48,6 +48,8 @@ class ScriptValue : public IntrusivePtrBase { virtual operator double() const; /// Convert this value to an integer virtual operator int() const; + /// Convert this value to a boolean + inline operator bool() const { return (int)*this; } /// Convert this value to a color virtual operator Color() const; diff --git a/src/util/for_each.hpp b/src/util/for_each.hpp index 527972d4..19ad20db 100644 --- a/src/util/for_each.hpp +++ b/src/util/for_each.hpp @@ -85,12 +85,19 @@ } /// Declare typeof magic for a specific std::vector type - #define DECLARE_TYPEOF_COLLECTION(T) DECLARE_TYPEOF(vector); \ -// DECLARE_TYPEOF_CONST(set) + #define DECLARE_TYPEOF_COLLECTION(T) DECLARE_TYPEOF(vector< T >); \ + DECLARE_TYPEOF_CONST(set< T >) #endif - +/// Use for template classes +/** i.e. + * DECLARE_TYPEOF(pair); + * instead of + * DECLARE_TYPEOF(pair); + */ +#define COMMA , + // ----------------------------------------------------------------------------- : Looping macros with iterators /// Iterate over a collection, using an iterator it of type Type diff --git a/src/util/locale.hpp b/src/util/locale.hpp index 6bc6596e..64ddd07a 100644 --- a/src/util/locale.hpp +++ b/src/util/locale.hpp @@ -97,8 +97,9 @@ String tr(const SymbolFont&, const String& key, const String& def); inline String format_string(const String& format, ...) { va_list args; va_start(args, format); - return String::Format(format, args); + String res = String::Format(format, args); va_end(args); + return res; } inline String format_string(const String& format, const String& a0) { return String::Format(format, a0.c_str()); diff --git a/src/util/platform.hpp b/src/util/platform.hpp index 59ccdd4d..2f3fdb67 100644 --- a/src/util/platform.hpp +++ b/src/util/platform.hpp @@ -9,7 +9,7 @@ /** @file util/platform.hpp * - * @brief Platform specific hacks + * @brief Platform and compiler specific hacks. */ // ----------------------------------------------------------------------------- : Includes @@ -23,11 +23,22 @@ #ifdef __linux__ + /// wxMkDir as documented inline void wxMkDir(const String& dir) { wxMkDir(wxConvLocal.cWX2MB(dir), 0777); } #endif +// ----------------------------------------------------------------------------- : GCC + +#ifdef __GNUC__ + + /// Absolute value of integers + template + inline T abs(T a) { return a < 0 ? -a : a; } + +#endif + // ----------------------------------------------------------------------------- : EOF #endif diff --git a/src/util/string.hpp b/src/util/string.hpp index 873b6cd3..f26253de 100644 --- a/src/util/string.hpp +++ b/src/util/string.hpp @@ -113,7 +113,8 @@ String capitalize_sentence(const String&); String cannocial_name_form(const String&); /// Returns the singular form of a string -/** Used for reflection, for example "vector apples" is written with keys "apple" +/** Used for reflection, for example "vector apples" is written with keys + * singular_form("apples"), which is "apple" */ String singular_form(const String&); diff --git a/src/util/tagged_string.cpp b/src/util/tagged_string.cpp index e4ebcf8b..48e28e92 100644 --- a/src/util/tagged_string.cpp +++ b/src/util/tagged_string.cpp @@ -121,6 +121,10 @@ size_t match_close_tag(const String& str, size_t start) { return String::npos; } +size_t match_close_tag_end(const String& str, size_t start) { + return skip_tag(str, match_close_tag(str, start)); +} + size_t last_start_tag_before(const String& str, const String& tag, size_t start) { start = min(str.size(), start); for (size_t pos = start ; pos > 0 ; --pos) { @@ -178,7 +182,7 @@ size_t index_to_cursor(const String& str, size_t index, Movement dir) { if (is_substr(str, i, _(" before && index < after) { // index is inside an atom, determine on which side we want the cursor if (dir == MOVE_RIGHT) { @@ -215,7 +219,7 @@ void cursor_to_index_range(const String& str, size_t cursor, size_t& start, size // a tag if (is_substr(str, i, _("