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<int COMMA string>). GCC doesn't need DECLARE_TYPEOF anyway.

Keyword expansion now works, still todo:
 - marking parameters, e.g. "Cycling 2W" -> "Cycling <param-mana>2W</param-mana>"
 - 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
This commit is contained in:
twanvl
2007-03-17 23:58:16 +00:00
parent 83b6aa36f8
commit 0464f5f7fc
29 changed files with 560 additions and 161 deletions
+3 -3
View File
@@ -9,8 +9,8 @@
#include <data/action/symbol.hpp>
#include <data/action/symbol_part.hpp>
DECLARE_TYPEOF_COLLECTION((pair<SymbolPartP,SymbolPartCombine>));
DECLARE_TYPEOF_COLLECTION((pair<SymbolPartP,size_t >));
DECLARE_TYPEOF_COLLECTION(pair<SymbolPartP COMMA SymbolPartCombine>);
DECLARE_TYPEOF_COLLECTION(pair<SymbolPartP COMMA size_t >);
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);
+1 -1
View File
@@ -13,7 +13,7 @@
#include <util/reflect.hpp>
DECLARE_TYPEOF_COLLECTION(FieldP);
DECLARE_TYPEOF_NO_REV((IndexMap<FieldP,ValueP>));
DECLARE_TYPEOF_NO_REV(IndexMap<FieldP COMMA ValueP>);
// ----------------------------------------------------------------------------- : Card
+1 -1
View File
@@ -10,7 +10,7 @@
#include <util/io/package.hpp>
DECLARE_TYPEOF_COLLECTION(ChoiceField::ChoiceP);
DECLARE_TYPEOF((map<String,ScriptableImage>));
DECLARE_TYPEOF(map<String COMMA ScriptableImage>);
// ----------------------------------------------------------------------------- : ChoiceField
+4 -3
View File
@@ -9,6 +9,7 @@
#include <data/format/image_to_symbol.hpp>
#include <gfx/bezier.hpp>
#include <util/error.hpp>
#include <util/platform.hpp>
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);
+310 -49
View File
@@ -10,7 +10,12 @@
#include <util/tagged_string.hpp>
class KeywordTrie;
DECLARE_TYPEOF((map<Char, KeywordTrie*>));
DECLARE_TYPEOF(map<Char COMMA KeywordTrie*>);
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<KeywordParamP>& param_types, bool force) {
if (!force && matchRe.IsValid()) return;
parameters.clear();
// Prepare regex
// String regex;
vector<KeywordParamP>::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, _("<param"))) {
// parameter, determine type...
size_t start = skip_tag(match, i), end = match_close_tag(match, i);
String type = match.substr(start, end-start);
// find parameter type 'type'
KeywordParam* p = nullptr;
FOR_EACH_CONST(pt, param_types) {
if (pt->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<Char, KeywordTrie*> children; ///< children after a given character (owned)
KeywordTrie* on_any_star; ///< children on /.*/ (owned)
Keyword* finished; ///< keywords that end in this node
map<Char, KeywordTrie*> children; ///< children after a given character (owned)
KeywordTrie* on_any_star; ///< children on /.*/ (owned or this)
vector<const KeywordExpansion*> 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<KeywordDatabase>();
}
void add_keyword(KeywordDatabase& db, const Keyword& kw) {
db.addKeyword(kw);
void KeywordDatabase::clear() {
delete root;
root = nullptr;
}
void KeywordDatabase::add(const vector<KeywordP>& 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, _("<atom-keyword>"));
// 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, _("<param"))) {
// tag, parameter, match anything
cur = cur->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<KeywordParamP>& ps, const vector<KeywordP>& kws) {
FOR_EACH_CONST(kw, kws) {
prepare_parameters(ps, *kw);
}
}
void KeywordDatabase::prepare_parameters(const vector<KeywordParamP>& 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<KeywordTrie*>& 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, _("<atom-reminder>"));
s = remove_tag_contents(s, _("<atom-keyword>")); // OLD, TODO: REMOVEME
s = remove_tag(s, _("<keyword-param"));
String untagged = untag_no_escape(s);
if (!root) return s;
String result;
// Find keywords
while (!s.empty()) {
vector<KeywordTrie*> current; // current location(s) in the trie
vector<KeywordTrie*> next; // location(s) after this step
set<const KeywordExpansion*> used; // keywords already investigated
current.push_back(root);
closure(current);
char expand_type = 'a'; // is the keyword expanded? From <kw-?> 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, _("<kw-")) && i + 4 < s.size()) {
expand_type = s.GetChar(i + 4); // <kw-?>
s = s.erase(i, skip_tag(s,i)-i); // remove the tag from the string
} else if (is_substr(s, i, _("</kw-"))) {
expand_type = 'a';
s = s.erase(i, skip_tag(s,i)-i); // remove the tag from the string
} else {
i = skip_tag(s, i);
}
continue;
} else {
++i;
}
// find 'next' trie node set matching c
FOR_EACH(kt, current) {
if (kt->on_any_star) {
next.push_back(kt->on_any_star);
}
map<Char,KeywordTrie*>::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 += _("<kw-"); result += expand_type; result += _(">");
result += combine_script->eval(ctx)->toString();
result += _("</kw-"); result += expand_type; result += _(">");
} else {
result += _("<kw-"); result += expand_type; result += _(">");
result += s.substr(start, end - start);
result += _("</kw-"); result += expand_type; 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;
}
+47 -17
View File
@@ -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, <param> tags are used for parameters
vector<KeywordParamP> 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&parameters are okay
*/
void prepare(const vector<KeywordParamP>& param_types, bool force = false);
DECLARE_REFLECTION();
};
@@ -64,7 +74,7 @@ class Keyword {
String keyword; ///< The keyword
vector<KeywordExpansionP> 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<KeywordP>&);
/// 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<KeywordParamP>&, const vector<KeywordP>&);
static void prepare_parameters(const vector<KeywordParamP>&, 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
+1 -1
View File
@@ -19,7 +19,7 @@
#include <wx/sstream.h>
DECLARE_TYPEOF_COLLECTION(CardP);
DECLARE_TYPEOF_NO_REV((IndexMap<FieldP,ValueP>));
DECLARE_TYPEOF_NO_REV(IndexMap<FieldP COMMA ValueP>);
// ----------------------------------------------------------------------------- : Set
+2
View File
@@ -14,6 +14,7 @@
#include <util/action_stack.hpp>
#include <util/io/package.hpp>
#include <data/field.hpp> // for Set::value
#include <data/keyword.hpp>
#include <boost/scoped_ptr.hpp>
DECLARE_POINTER_TYPE(Card);
@@ -57,6 +58,7 @@ class Set : public Packaged {
vector<KeywordP> 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! */