Commit 986a6a2c authored by twanvl's avatar twanvl

Added support for separators that are part of a parameter;

Keywords match whole words only;
Added english_singular/plural functions;
Fixed possible infinite loop in reader
parent ca2737ca
...@@ -152,15 +152,15 @@ init script: ...@@ -152,15 +152,15 @@ init script:
}; };
# replaces — correctly # replaces — correctly
alternative_cost := replace_rule(match:"—|\\.", replace:"") + replace_rule(match:"[A-Z]", in_context:"<param-[a-z]*><match>", replace: { to_lower() }) alternative_cost := replace_rule(match:"\\.$", replace:"") + replace_rule(match:"^[A-Z]", replace: { to_lower() })
mana_cost := replace_rule(match:"<param-cost> ", replace: "<param-cost>")
add := "" # default is nothing add := "" # default is nothing
format_cost := { for_mana_costs := format_cost := {
if substring(begin: 0, end: 13)=="<param-cost>—" then if input.separator=="—" then
alternative_cost() "<param-cost>{alternative_cost(input.param)}</param-cost>"
else add + mana_cost() else
}; "<param-mana>{add}{input.param}</param-mana>"
format_cost_start := replace_rule(match:"^(<param-cost>)?[ ]?-", replace:"\\1—") }
long_dash := replace_rule(match:"-", replace:"—")
# Utilities for keywords # Utilities for keywords
has_cc := { card.casting_cost != "" } has_cc := { card.casting_cost != "" }
has_pt := { card.pt != "" } has_pt := { card.pt != "" }
...@@ -237,10 +237,6 @@ init script: ...@@ -237,10 +237,6 @@ init script:
replace_rule( replace_rule(
match: "AE", match: "AE",
replace: "Æ" ) + replace: "Æ" ) +
# step 6c : "s" remover for keywords
replace_rule(
match: "s</param-name><s>"
replace: "" ) +
# step 7 : italic reminder text # step 7 : italic reminder text
replace_rule( replace_rule(
match: "[(][^)\n]*[)]?", match: "[(][^)\n]*[)]?",
...@@ -1125,23 +1121,22 @@ keyword parameter type: ...@@ -1125,23 +1121,22 @@ keyword parameter type:
keyword parameter type: keyword parameter type:
name: cost name: cost
match: [ ][STXYZ0-9WUBRG/|]*|[-—][^(\n]* match: [ ][STXYZ0-9WUBRG/|]*|[-—][^(\n]*
separator before is: [ —-]
optional: false
# note: the separator is part of match
refer script: refer script:
name: normal name: normal
description: standard cost
script: \{{input}\}
refer script:
name: add nothing for mana costs
description: When using mana only costs, doesn't include anything extra in the reminder text description: When using mana only costs, doesn't include anything extra in the reminder text
script: \{format_cost({input})\} script: \{{input}\}
refer script: refer script:
name: add "pay an additional " for mana costs name: add "pay an additional " for mana costs
description: When using mana only costs, words the reminder text as "pay an additional <cost>" description: When using mana only costs, words the reminder text as "pay an additional <cost>"
script: \{format_cost(add:"pay an additional ",{input})\} script: \{for_mana_costs(add:"pay an additional ",{input})\}
refer script: refer script:
name: add "pay " for mana costs name: add "pay " for mana costs
description: When using mana only costs, words the reminder text as "pay <cost>" description: When using mana only costs, words the reminder text as "pay <cost>"
script: \{format_cost(add:"pay ",{input})\} script: \{for_mana_costs(add:"pay ",{input})\}
script: format_cost_start() separator script: long_dash()
keyword parameter type: keyword parameter type:
name: number name: number
match: [XYZ0-9]+ match: [XYZ0-9]+
...@@ -1204,9 +1199,9 @@ keyword: ...@@ -1204,9 +1199,9 @@ keyword:
reminder: Target a {param1} as you play this. This card comes into play attached to that {param1}. reminder: Target a {param1} as you play this. This card comes into play attached to that {param1}.
keyword: keyword:
keyword: Cycling keyword: Cycling
match: Cycling<atom-param>cost</atom-param> match: Cycling <atom-param>cost</atom-param>
mode: expert mode: expert
reminder: {format_cost(param1)}, Discard this card: Draw a card. reminder: {param1}, Discard this card: Draw a card.
keyword: keyword:
keyword: Trample keyword: Trample
match: Trample match: Trample
...@@ -1239,9 +1234,9 @@ keyword: ...@@ -1239,9 +1234,9 @@ keyword:
reminder: This creature can’t attack. reminder: This creature can’t attack.
keyword: keyword:
keyword: Cumulative upkeep keyword: Cumulative upkeep
match: Cumulative upkeep<atom-param>cost</atom-param> match: Cumulative upkeep <atom-param>cost</atom-param>
mode: old mode: old
reminder: At the beginning of your upkeep, put an age counter on this permanent, then sacrifice it unless you {format_cost(add:"pay ",param1)} for each age counter on it. reminder: At the beginning of your upkeep, put an age counter on this permanent, then sacrifice it unless you {for_mana_costs(add:"pay ",param1)} for each age counter on it.
keyword: keyword:
keyword: Horsemanship keyword: Horsemanship
match: Horsemanship match: Horsemanship
...@@ -1264,19 +1259,19 @@ keyword: ...@@ -1264,19 +1259,19 @@ keyword:
reminder: This creature can block or be blocked only by creatures with shadow. reminder: This creature can block or be blocked only by creatures with shadow.
keyword: keyword:
keyword: Buyback keyword: Buyback
match: Buyback<atom-param>cost</atom-param> match: Buyback <atom-param>cost</atom-param>
mode: expert mode: expert
reminder: You may {format_cost(add:"pay ",param1)} in addition to any other costs as you play this spell. If you do, put this card into your hand as it resolves. reminder: You may {for_mana_costs(add:"pay ",param1)} in addition to any other costs as you play this spell. If you do, put this card into your hand as it resolves.
keyword: keyword:
keyword: Echo keyword: Echo
match: Echo<atom-param>cost</atom-param> match: Echo <atom-param>cost</atom-param>
mode: expert mode: expert
reminder: At the beginning of your upkeep, if this came under your control since the beginning of your last upkeep, sacrifice it unless you pay its echo cost. reminder: At the beginning of your upkeep, if this came under your control since the beginning of your last upkeep, sacrifice it unless you pay its echo cost.
keyword: keyword:
keyword: Cardcycling keyword: Cardcycling
match: <atom-param>prefix</atom-param>cycling<atom-param>cost</atom-param> match: <atom-param>prefix</atom-param>cycling <atom-param>cost</atom-param>
mode: expert mode: expert
reminder: {format_cost(param2)}, Discard this card: Search your library for a {param1} card, reveal it, and put it into your hand. Then shuffle your library. reminder: {param2}, Discard this card: Search your library for a {param1} card, reveal it, and put it into your hand. Then shuffle your library.
keyword: keyword:
keyword: Fading keyword: Fading
match: Fading <atom-param>number</atom-param> match: Fading <atom-param>number</atom-param>
...@@ -1284,22 +1279,22 @@ keyword: ...@@ -1284,22 +1279,22 @@ keyword:
reminder: This comes into play with {english_number_a(param1)} fade counter(s) on it. At the beginning of your upkeep, remove a fade counter from it. If you can’t, sacrifice it. reminder: This comes into play with {english_number_a(param1)} fade counter(s) on it. At the beginning of your upkeep, remove a fade counter from it. If you can’t, sacrifice it.
keyword: keyword:
keyword: Kicker keyword: Kicker
match: Kicker<atom-param>cost</atom-param> match: Kicker <atom-param>cost</atom-param>
mode: expert mode: expert
reminder: You may {format_cost(add:"pay an additional ",param1)} as you play this spell. reminder: You may {for_mana_costs(add:"pay an additional ",param1)} as you play this spell.
keyword: keyword:
keyword: Madness keyword: Madness
match: Madness<atom-param>cost</atom-param> match: Madness <atom-param>cost</atom-param>
mode: expert mode: expert
reminder: You may play this card for its madness cost at the time you discard it. reminder: You may play this card for its madness cost at the time you discard it.
keyword: keyword:
keyword: Flashback keyword: Flashback
match: Flashback<atom-param>cost</atom-param> match: Flashback <atom-param>cost</atom-param>
mode: expert mode: expert
reminder: You may play this card from your graveyard for its flashback cost. Then remove it from the game. reminder: You may play this card from your graveyard for its flashback cost. Then remove it from the game.
keyword: keyword:
keyword: Morph keyword: Morph
match: Morph<atom-param>cost</atom-param> match: Morph <atom-param>cost</atom-param>
mode: expert mode: expert
reminder: You may play this face down as a 2/2 creature for <sym>3</sym>. Turn it face up any time for its morph cost. reminder: You may play this face down as a 2/2 creature for <sym>3</sym>. Turn it face up any time for its morph cost.
keyword: keyword:
...@@ -1326,17 +1321,17 @@ keyword: ...@@ -1326,17 +1321,17 @@ keyword:
keyword: Affinity for keyword: Affinity for
match: Affinity for <atom-param>name</atom-param> match: Affinity for <atom-param>name</atom-param>
mode: expert mode: expert
reminder: This spell costs <sym>1</sym> less to play for each {param1}<s> you control. reminder: This spell costs <sym>1</sym> less to play for each {english_singular(param1)} you control.
keyword: keyword:
keyword: Entwine keyword: Entwine
match: Entwine<atom-param>cost</atom-param> match: Entwine <atom-param>cost</atom-param>
mode: expert mode: expert
reminder: Choose both if you pay the entwine cost. reminder: Choose both if you pay the entwine cost.
keyword: keyword:
keyword: Equip keyword: Equip
match: Equip<atom-param>cost</atom-param> match: Equip <atom-param>cost</atom-param>
mode: expert mode: expert
reminder: {format_cost(param1)}: Attach to target creature you control. Equip only as a sorcery. reminder: {param1}: Attach to target creature you control. Equip only as a sorcery.
keyword: keyword:
keyword: Imprint keyword: Imprint
match: Imprint—<atom-param>action</atom-param> match: Imprint—<atom-param>action</atom-param>
...@@ -1380,9 +1375,9 @@ keyword: ...@@ -1380,9 +1375,9 @@ keyword:
reminder: When this blocks or becomes blocked, it gets +{param1}/+{param1} until end of turn. reminder: When this blocks or becomes blocked, it gets +{param1}/+{param1} until end of turn.
keyword: keyword:
keyword: Ninjutsu keyword: Ninjutsu
match: Ninjutsu<atom-param>cost</atom-param> match: Ninjutsu <atom-param>cost</atom-param>
mode: expert mode: expert
reminder: {format_cost(param1)}, Return an unblocked attacker you control to hand: Put this card into play from your hand tapped and attacking. reminder: {param1}, Return an unblocked attacker you control to hand: Put this card into play from your hand tapped and attacking.
keyword: keyword:
keyword: Soulshift keyword: Soulshift
match: Soulshift <atom-param>number</atom-param> match: Soulshift <atom-param>number</atom-param>
...@@ -1400,9 +1395,9 @@ keyword: ...@@ -1400,9 +1395,9 @@ keyword:
reminder: Each creature you tap while playing this spell reduces its cost by <sym>1</sym> or by one mana of that creature’s color. reminder: Each creature you tap while playing this spell reduces its cost by <sym>1</sym> or by one mana of that creature’s color.
keyword: keyword:
keyword: Transmute keyword: Transmute
match: Transmute<atom-param>cost</atom-param> match: Transmute <atom-param>cost</atom-param>
mode: expert mode: expert
reminder: {format_cost(param1)}, Discard this card: Search your library for a card with the same converted mana cost as this card, reveal it, and put it into your hand. Then shuffle your library. Play only as a sorcery. reminder: {param1}, Discard this card: Search your library for a card with the same converted mana cost as this card, reveal it, and put it into your hand. Then shuffle your library. Play only as a sorcery.
keyword: keyword:
keyword: Haunt keyword: Haunt
match: Haunt match: Haunt
...@@ -1415,7 +1410,7 @@ keyword: ...@@ -1415,7 +1410,7 @@ keyword:
reminder: If an opponent was dealt damage this turn, this creature comes into play with {english_number_a(param1)} +1/+1 counter(s) on it. reminder: If an opponent was dealt damage this turn, this creature comes into play with {english_number_a(param1)} +1/+1 counter(s) on it.
keyword: keyword:
keyword: Replicate keyword: Replicate
match: Replicate<atom-param>cost</atom-param> match: Replicate <atom-param>cost</atom-param>
mode: expert mode: expert
reminder: When you play this spell, copy it for each time you paid its replicate cost. You may choose new targets for the copies. reminder: When you play this spell, copy it for each time you paid its replicate cost. You may choose new targets for the copies.
keyword: keyword:
...@@ -1432,7 +1427,7 @@ keyword: ...@@ -1432,7 +1427,7 @@ keyword:
keyword: Protection from keyword: Protection from
match: Protection from <atom-param>name</atom-param> match: Protection from <atom-param>name</atom-param>
mode: core mode: core
reminder: This creature can’t be blocked, targeted, dealt damage, or enchanted, or equipped by anything {param1}<s>. reminder: This creature can’t be blocked, targeted, dealt damage, or enchanted, or equipped by anything {english_singular(param1)}.
keyword: keyword:
keyword: Dredge keyword: Dredge
match: Dredge <atom-param>number</atom-param> match: Dredge <atom-param>number</atom-param>
...@@ -1445,14 +1440,14 @@ keyword: ...@@ -1445,14 +1440,14 @@ keyword:
reminder: This creature comes into play with {english_number_a(param1)} +1/+1 counter(s) on it. Whenever another creature comes into play, you may move a +1/+1 counter from this creature onto it. reminder: This creature comes into play with {english_number_a(param1)} +1/+1 counter(s) on it. Whenever another creature comes into play, you may move a +1/+1 counter from this creature onto it.
keyword: keyword:
keyword: Forecast keyword: Forecast
match: Forecast<atom-param>cost</atom-param>, Reveal <atom-param>name</atom-param> from your hand: <atom-param>action</atom-param> match: Forecast <atom-param>cost</atom-param>, Reveal <atom-param>name</atom-param> from your hand: <atom-param>action</atom-param>
mode: expert mode: expert
reminder: Play this ability only during your upkeep and only once each turn. reminder: Play this ability only during your upkeep and only once each turn.
keyword: keyword:
keyword: Recover keyword: Recover
match: Recover<atom-param>cost</atom-param> match: Recover <atom-param>cost</atom-param>
mode: expert mode: expert
reminder: When a creature is put into your graveyard from play, you may {format_cost(add:"pay ",param1)}. If you do, return this card from your graveyard to your hand. Otherwise, remove this card from the game. reminder: When a creature is put into your graveyard from play, you may {for_mana_costs(add:"pay ",param1)}. If you do, return this card from your graveyard to your hand. Otherwise, remove this card from the game.
keyword: keyword:
keyword: Ripple keyword: Ripple
match: Ripple <atom-param>number</atom-param> match: Ripple <atom-param>number</atom-param>
...@@ -1510,14 +1505,14 @@ keyword: ...@@ -1510,14 +1505,14 @@ keyword:
reminder: Look at the top {english_number_multiple(param1)} card(s) of an opponent's library, then put any number of them on the bottom of that player's library and the rest on top in any order. reminder: Look at the top {english_number_multiple(param1)} card(s) of an opponent's library, then put any number of them on the bottom of that player's library and the rest on top in any order.
keyword: keyword:
keyword: Transfigure keyword: Transfigure
match: Transfigure<atom-param>cost</atom-param> match: Transfigure <atom-param>cost</atom-param>
mode: expert mode: expert
reminder: {format_cost(param1)}, Sacrifice this creature: Search your library for a creature card with the same converted mana cost as this creature and put that card into play. Then shuffle your library. Play only as a sorcery. reminder: {param1}, Sacrifice this creature: Search your library for a creature card with the same converted mana cost as this creature and put that card into play. Then shuffle your library. Play only as a sorcery.
keyword: keyword:
keyword: Aura swap keyword: Aura swap
match: Aura swap<atom-param>cost</atom-param> match: Aura swap <atom-param>cost</atom-param>
mode: expert mode: expert
reminder: {format_cost(param1)}: Exchange this Aura with an Aura card in your hand. reminder: {param1}: Exchange this Aura with an Aura card in your hand.
keyword: keyword:
keyword: Frenzy keyword: Frenzy
match: Frenzy <atom-param>number</atom-param> match: Frenzy <atom-param>number</atom-param>
...@@ -1568,6 +1563,6 @@ keyword: ...@@ -1568,6 +1563,6 @@ keyword:
mode: pseudo mode: pseudo
keyword: keyword:
keyword: Fortify keyword: Fortify
match: Fortify<atom-param>cost</atom-param> match: Fortify <atom-param>cost</atom-param>
mode: expert mode: expert
reminder: {format_cost(param1)}: Attach to target land you control. Fortify only as a sorcery. This card comes into play unattached and stays in play if the land leaves play. reminder: {param1}: Attach to target land you control. Fortify only as a sorcery. This card comes into play unattached and stays in play if the land leaves play.
...@@ -16,11 +16,13 @@ DECLARE_TYPEOF_COLLECTION(KeywordP); ...@@ -16,11 +16,13 @@ DECLARE_TYPEOF_COLLECTION(KeywordP);
DECLARE_TYPEOF_COLLECTION(KeywordModeP); DECLARE_TYPEOF_COLLECTION(KeywordModeP);
DECLARE_TYPEOF_COLLECTION(KeywordParamP); DECLARE_TYPEOF_COLLECTION(KeywordParamP);
DECLARE_TYPEOF_COLLECTION(const Keyword*); DECLARE_TYPEOF_COLLECTION(const Keyword*);
DECLARE_POINTER_TYPE(KeywordParamValue);
// ----------------------------------------------------------------------------- : Reflection // ----------------------------------------------------------------------------- : Reflection
KeywordParam::KeywordParam() KeywordParam::KeywordParam()
: optional(true) : optional(true)
, eat_separator(true)
{} {}
IMPLEMENT_REFLECTION(ParamReferenceType) { IMPLEMENT_REFLECTION(ParamReferenceType) {
...@@ -35,7 +37,11 @@ IMPLEMENT_REFLECTION(KeywordParam) { ...@@ -35,7 +37,11 @@ IMPLEMENT_REFLECTION(KeywordParam) {
REFLECT(placeholder); REFLECT(placeholder);
REFLECT(optional); REFLECT(optional);
REFLECT(match); REFLECT(match);
REFLECT(separator_before_is);
REFLECT(eat_separator);
REFLECT(script); REFLECT(script);
REFLECT(reminder_script);
REFLECT(separator_script);
REFLECT(example); REFLECT(example);
REFLECT(refer_scripts); REFLECT(refer_scripts);
} }
...@@ -77,6 +83,49 @@ IMPLEMENT_REFLECTION(Keyword) { ...@@ -77,6 +83,49 @@ IMPLEMENT_REFLECTION(Keyword) {
REFLECT(mode); REFLECT(mode);
} }
/*//%%
String KeywordParam::make_separator_before() const {
// decode regex; find a string that matches it
String ret;
int disabled = 0;
for (size_t i = 0 ; i < separator_before_is.size() ; ++i) {
Char c = separator_before_is.GetChar(i);
if (c == _('(')) {
if (disabled) ++disabled;
} else if (c == _(')')) {
if (disabled) --disabled;
} else if (!disabled) {
if (c == _('|')) {
disabled = 1; // disable after |
} else if (c == _('+') || c == _('*') || c == _('?') || c == _('^') || c == _('$')) {
// ignore
} else if (c == _('\\') && i + 1 < separator_before_is.size()) {
// escape
ret += separator_before_is.GetChar(++i);
} else if (c == _('[') && i + 1 < separator_before_is.size()) {
// character class
c = separator_before_is.GetChar(++i);
if (c != _('^')) ret += c;
// ignore the rest of the class
for ( ++i ; i < separator_before_is.size() ; ++i) {
c = separator_before_is.GetChar(i);
if (c == _(']')) break;
}
} else {
ret += c;
}
}
}
return ret;
}*/
void KeywordParam::compile() {
if (!separator_before_is.empty() && !separator_before_re.IsValid()) {
separator_before_re.Compile(_("^") + separator_before_is, wxRE_ADVANCED);
if (eat_separator) {
separator_before_eat.Compile(separator_before_is + _("$"), wxRE_ADVANCED);
}
}
}
size_t Keyword::findMode(const vector<KeywordModeP>& modes) const { size_t Keyword::findMode(const vector<KeywordModeP>& modes) const {
// find // find
...@@ -139,12 +188,19 @@ String regex_escape(Char c) { ...@@ -139,12 +188,19 @@ String regex_escape(Char c) {
return String(1,c); return String(1,c);
} }
} }
/// Escape a string for use in regular expressions
String regex_escape(const String& s) {
String ret;
FOR_EACH_CONST(c,s) ret += regex_escape(c);
return ret;
}
void Keyword::prepare(const vector<KeywordParamP>& param_types, bool force) { void Keyword::prepare(const vector<KeywordParamP>& param_types, bool force) {
if (!force && matchRe.IsValid()) return; if (!force && match_re.IsValid()) return;
parameters.clear(); parameters.clear();
// Prepare regex // Prepare regex
String regex = _("("); String regex;
String text; // normal, non-regex, text
vector<KeywordParamP>::const_iterator param = parameters.begin(); vector<KeywordParamP>::const_iterator param = parameters.begin();
// Parse the 'match' string // Parse the 'match' string
for (size_t i = 0 ; i < match.size() ;) { for (size_t i = 0 ; i < match.size() ;) {
...@@ -162,21 +218,37 @@ void Keyword::prepare(const vector<KeywordParamP>& param_types, bool force) { ...@@ -162,21 +218,37 @@ void Keyword::prepare(const vector<KeywordParamP>& param_types, bool force) {
} }
} }
if (!p) { if (!p) {
throw InternalError(_("Unknown keyword parameter type: ") + type); // throwing an error can mean a set will not be loaded!
// instead, simply disable the keyword
//throw InternalError(_("Unknown keyword parameter type: ") + type);
handle_error(_("Unknown keyword parameter type: ") + type, true, false);
valid = false;
return;
} }
parameters.push_back(p); parameters.push_back(p);
// modify regex // modify regex : match text before
regex += _(")(") + make_non_capturing(p->match) + _(")") + (p->optional ? _("?") : _("")) + _("("); p->compile();
if (p->separator_before_eat.IsValid() && p->separator_before_eat.Matches(text)) {
// remove the separator from the text to prevent duplicates
size_t start, len;
p->separator_before_eat.GetMatch(&start, &len);
text = text.substr(0, start);
}
regex += _("(") + regex_escape(text) + _(")");
text.clear();
// modify regex : match parameter
regex += _("(") + make_non_capturing(p->match) + (p->optional ? _(")?") : _(")"));
i = skip_tag(match, end); i = skip_tag(match, end);
} else { } else {
regex += regex_escape(c); text += c;
i++; i++;
} }
} }
regex += _(")"); regex += _("(") + regex_escape(text) + _(")");
if (matchRe.Compile(regex, wxRE_ADVANCED)) { regex = _("\\y") + regex + _("(?=$|[^a-zA-Z0-9])"); // only match whole words
if (match_re.Compile(regex, wxRE_ADVANCED)) {
// not valid if it matches "", that would make MSE hang // not valid if it matches "", that would make MSE hang
valid = !matchRe.Matches(_("")); valid = !match_re.Matches(_(""));
} else { } else {
valid = false; valid = false;
throw InternalError(_("Error creating match regex")); throw InternalError(_("Error creating match regex"));
...@@ -263,22 +335,40 @@ void KeywordDatabase::add(const vector<KeywordP>& kws) { ...@@ -263,22 +335,40 @@ void KeywordDatabase::add(const vector<KeywordP>& kws) {
void KeywordDatabase::add(const Keyword& kw) { void KeywordDatabase::add(const Keyword& kw) {
if (kw.match.empty() || !kw.valid) return; // can't handle empty keywords if (kw.match.empty() || !kw.valid) return; // can't handle empty keywords
// Create root
if (!root) { if (!root) {
root = new KeywordTrie; root = new KeywordTrie;
root->on_any_star = root; root->on_any_star = root;
} }
KeywordTrie* cur = root->insertAnyStar(); KeywordTrie* cur = root->insertAnyStar();
// Add to trie
String text; // normal text
size_t param = 0;
for (size_t i = 0 ; i < kw.match.size() ;) { for (size_t i = 0 ; i < kw.match.size() ;) {
Char c = kw.match.GetChar(i); Char c = kw.match.GetChar(i);
if (is_substr(kw.match, i, _("<atom-param"))) { if (is_substr(kw.match, i, _("<atom-param"))) {
// tag, parameter, match anything // parameter, is there a separator we should eat?
if (param < kw.parameters.size()) {
wxRegEx& sep = kw.parameters[param]->separator_before_eat;
if (sep.IsValid() && sep.Matches(text)) {
// remove the separator from the text to prevent duplicates
size_t start, len;
sep.GetMatch(&start, &len);
text = text.substr(0, start);
}
}
++param;
// match anything
cur = cur->insert(text);
text.clear();
cur = cur->insertAnyStar(); cur = cur->insertAnyStar();
i = match_close_tag_end(kw.match, i); i = match_close_tag_end(kw.match, i);
} else { } else {
cur = cur->insert(c); text += c;
i++; i++;
} }
} }
cur = cur->insert(text);
// now cur is the trie after matching the keyword anywhere in the input text // now cur is the trie after matching the keyword anywhere in the input text
cur->finished.push_back(&kw); cur->finished.push_back(&kw);
} }
...@@ -383,26 +473,27 @@ String KeywordDatabase::expand(const String& text, ...@@ -383,26 +473,27 @@ String KeywordDatabase::expand(const String& text,
const Keyword* kw = f; const Keyword* kw = f;
if (used.insert(kw).second) { if (used.insert(kw).second) {
// we have found a possible match, which we have not seen before // we have found a possible match, which we have not seen before
assert(kw->matchRe.IsValid()); assert(kw->match_re.IsValid());
// try to match it against the *untagged* string // try to match it against the *untagged* string
if (kw->matchRe.Matches(untagged)) { if (kw->match_re.Matches(untagged)) {
// Everything before the keyword // Everything before the keyword
size_t start_u, len_u; size_t start_u, len_u;
kw->matchRe.GetMatch(&start_u, &len_u, 0); kw->match_re.GetMatch(&start_u, &len_u, 0);
size_t start = untagged_to_index(s, start_u, true), size_t start = untagged_to_index(s, start_u, true),
end = untagged_to_index(s, start_u + len_u, true); end = untagged_to_index(s, start_u + len_u, true);
result += s.substr(0, start); result += s.substr(0, start);
// Split the keyword, set parameters in context // Split the keyword, set parameters in context
String total; // the total keyword String total; // the total keyword
size_t match_count = kw->matchRe.GetMatchCount(); size_t match_count = kw->match_re.GetMatchCount();
assert(match_count - 1 == 1 + 2 * kw->parameters.size()); assert(match_count - 1 == 1 + 2 * kw->parameters.size());
for (size_t j = 1 ; j < match_count ; ++j) { for (size_t j = 1 ; j < match_count ; ++j) {
// j = odd -> text // we start counting at 1, so
// j = even -> parameter #(j/2) // j = 1 mod 2 -> text
// j = 0 mod 2 -> parameter #((j-1)/2) == (j/2-1)
size_t start_u, len_u; size_t start_u, len_u;
kw->matchRe.GetMatch(&start_u, &len_u, j); kw->match_re.GetMatch(&start_u, &len_u, j);
// note: start_u can be (uint)-1 when len_u == 0 // note: start_u can be (uint)-1 when len_u == 0
size_t part_end = len_u > 0 ? untagged_to_index(s, start_u + len_u, true) : start; size_t part_end = len_u > 0 ? untagged_to_index(s, start_u + len_u, true) : start;
String part = s.substr(start, part_end - start); String part = s.substr(start, part_end - start);
...@@ -410,24 +501,45 @@ String KeywordDatabase::expand(const String& text, ...@@ -410,24 +501,45 @@ String KeywordDatabase::expand(const String& text,
// parameter // parameter
KeywordParam& kwp = *kw->parameters[j/2-1]; KeywordParam& kwp = *kw->parameters[j/2-1];
String param = untagged.substr(start_u, len_u); // untagged version String param = untagged.substr(start_u, len_u); // untagged version
// strip separator
String separator;
if (kwp.separator_before_re.IsValid()) {
if (kwp.separator_before_re.Matches(param)) {
size_t s_start, s_len; // start should be 0
kwp.separator_before_re.GetMatch(&s_start, &s_len);
separator = param.substr(0, s_start + s_len);
param = param.substr(s_start + s_len);
// strip from tagged version
size_t end_t = untagged_to_index(part, s_start + s_len, false);
part = get_tags(part, 0, end_t, true, true) + part.substr(end_t);
// transform?
if (kwp.separator_script) {
ctx.setVariable(_("input"), to_script(separator));
separator = kwp.separator_script.invoke(ctx)->toString();
}
}
}
// to script
KeywordParamValueP script_param(new KeywordParamValue(kwp.name, separator, param));
KeywordParamValueP script_part (new KeywordParamValue(kwp.name, separator, part));
// process param
if (param.empty()) { if (param.empty()) {
// placeholder // placeholder
param = _("<atom-kwpph>") + (kwp.placeholder.empty() ? kwp.name : kwp.placeholder) + _("</atom-kwpph>"); script_param->value = _("<atom-kwpph>") + (kwp.placeholder.empty() ? kwp.name : kwp.placeholder) + _("</atom-kwpph>");
part = part + param; // keep tags script_part->value = part + script_param->value; // keep tags
} else if (kw->parameters[j/2-1]->script) { } else {
// apply parameter script // apply parameter script
ctx.setVariable(_("input"), to_script(part)); if (kwp.script) {
part = kwp.script.invoke(ctx)->toString(); ctx.setVariable(_("input"), script_part);
ctx.setVariable(_("input"), to_script(part)); script_part->value = kwp.script.invoke(ctx)->toString();
param = kwp.script.invoke(ctx)->toString(); }
if (kwp.reminder_script) {
ctx.setVariable(_("input"), script_param);
script_param->value = kwp.reminder_script.invoke(ctx)->toString();
}
} }
String param_type = replace_all(replace_all(replace_all(kwp.name, part = separator + script_part->toString();
_("("),_("-")), ctx.setVariable(String(_("param")) << (int)(j/2), script_param);
_(")"),_("-")),
_(" "),_("-"));
part = _("<param-") + param_type + _(">") + part + _("</param-") + param_type + _(">");
param = _("<param-") + param_type + _(">") + param + _("</param-") + param_type + _(">");
ctx.setVariable(String(_("param")) << (int)(j/2), to_script(param));
} }
total += part; total += part;
start = part_end; start = part_end;
...@@ -476,3 +588,22 @@ String KeywordDatabase::expand(const String& text, ...@@ -476,3 +588,22 @@ String KeywordDatabase::expand(const String& text,
return result; return result;
} }
// ----------------------------------------------------------------------------- : KeywordParamValue
ScriptType KeywordParamValue::type() const { return SCRIPT_STRING; }
String KeywordParamValue::typeName() const { return _("keyword parameter"); }
KeywordParamValue::operator String() const {
String safe_type = replace_all(replace_all(replace_all(type_name,
_("("),_("-")),
_(")"),_("-")),
_(" "),_("-"));
return _("<param-") + safe_type + _(">") + value + _("</param-") + safe_type + _(">");
}
ScriptValueP KeywordParamValue::getMember(const String& name) const {
if (name == _("type")) return to_script(type_name);
if (name == _("separator")) return to_script(separator);
if (name == _("value")) return to_script(value);
if (name == _("param")) return to_script(value);
return ScriptValue::getMember(name);
}
...@@ -34,15 +34,28 @@ class ParamReferenceType : public IntrusivePtrBase<ParamReferenceType> { ...@@ -34,15 +34,28 @@ class ParamReferenceType : public IntrusivePtrBase<ParamReferenceType> {
class KeywordParam : public IntrusivePtrBase<KeywordParam> { class KeywordParam : public IntrusivePtrBase<KeywordParam> {
public: public:
KeywordParam(); KeywordParam();
String name; ///< Name of the parameter type String name; ///< Name of the parameter type
String description; ///< Description of the parameter type String description; ///< Description of the parameter type
String placeholder; ///< Placholder for <atom-kwpph>, name is used if this is empty String placeholder; ///< Placholder for <atom-kwpph>, name is used if this is empty
bool optional; ///< Can this parameter be left out (a placeholder is then used) bool optional; ///< Can this parameter be left out (a placeholder is then used)
String match; ///< Regular expression to match String match; ///< Regular expression to match (including separators)
OptionalScript script; ///< Transformation of the value for showing in the reminder text String separator_before_is; ///< Regular expression of separator before the param
String example; ///< Example for the keyword editor wxRegEx separator_before_re; ///< Regular expression of separator before the param, compiled
wxRegEx separator_before_eat;///< Regular expression of separator before the param, if eat_separator
bool eat_separator; ///< Remove the separator from the match string if it also appears there (prevent duplicates)
OptionalScript script; ///< Transformation of the value for showing as the parameter
OptionalScript reminder_script; ///< Transformation of the value for showing in the reminder text
OptionalScript separator_script; ///< Transformation of the separator
String example; ///< Example for the keyword editor
vector<ParamReferenceTypeP> refer_scripts;///< Way to refer to a parameter from the reminder text script vector<ParamReferenceTypeP> refer_scripts;///< Way to refer to a parameter from the reminder text script
//% /// Make a string that can function as a separator before the parameter
//% /** This tries to decode the separator_before_is regex */
//% String make_separator_before() const;
/// Compile regexes
void compile();
DECLARE_REFLECTION(); DECLARE_REFLECTION();
}; };
...@@ -76,9 +89,9 @@ class Keyword : public IntrusivePtrVirtualBase { ...@@ -76,9 +89,9 @@ class Keyword : public IntrusivePtrVirtualBase {
/// Regular expression to match and split parameters, automatically generated. /// Regular expression to match and split parameters, automatically generated.
/** The regex has exactly 2 * parameters.size() + 1 captures (excluding the entire match, caputure 0), /** The regex has exactly 2 * parameters.size() + 1 captures (excluding the entire match, caputure 0),
* captures 1,3,... capture the plain text of the match string * captures 1,3,... capture the plain text of the match string
* captures 2,4,... capture the parameters * captures 2,4,... capture the separators and parameters
*/ */
wxRegEx matchRe; wxRegEx match_re;
bool fixed; ///< Is this keyword uneditable? (true for game keywods, false for set keywords) bool fixed; ///< Is this keyword uneditable? (true for game keywods, false for set keywords)
bool valid; ///< Is this keyword okay (reminder text compiles & runs; match does not match "") bool valid; ///< Is this keyword okay (reminder text compiles & runs; match does not match "")
...@@ -132,5 +145,23 @@ class KeywordDatabase { ...@@ -132,5 +145,23 @@ class KeywordDatabase {
KeywordTrie* root; ///< Data structure for finding keywords KeywordTrie* root; ///< Data structure for finding keywords
}; };
// ----------------------------------------------------------------------------- : Processing parameters
/// A script value containing the value of a keyword parameter
class KeywordParamValue : public ScriptValue {
public:
KeywordParamValue(const String& type, const String& separator, const String& value)
: type_name(type), separator(separator), value(value)
{}
String type_name;
String separator;
String value;
virtual ScriptType type() const;
virtual String typeName() const;
virtual operator String() const;
virtual ScriptValueP getMember(const String& name) const;
};
// ----------------------------------------------------------------------------- : EOF // ----------------------------------------------------------------------------- : EOF
#endif #endif
...@@ -70,7 +70,7 @@ String do_english_num(String input, String(*fun)(int)) { ...@@ -70,7 +70,7 @@ String do_english_num(String input, String(*fun)(int)) {
// a keyword parameter, of the form "<param->123</param->" // a keyword parameter, of the form "<param->123</param->"
size_t start = skip_tag(input, 0); size_t start = skip_tag(input, 0);
if (start != String::npos) { if (start != String::npos) {
size_t end = input.find_first_of(_('<'), start); size_t end = input.find_first_of(_('<'), start);
if (end != String::npos) { if (end != String::npos) {
String is = input.substr(start, end - start); String is = input.substr(start, end - start);
long i = 0; long i = 0;
...@@ -105,6 +105,54 @@ SCRIPT_FUNCTION(english_number_multiple) { ...@@ -105,6 +105,54 @@ SCRIPT_FUNCTION(english_number_multiple) {
SCRIPT_RETURN(do_english_num(input, english_number_multiple)); SCRIPT_RETURN(do_english_num(input, english_number_multiple));
} }
// ----------------------------------------------------------------------------- : Singular/plural
String english_singular(const String& str) {
if (str.size() > 3 && is_substr(str, str.size()-3, _("ies"))) {
return str.substr(0, str.size() - 3) + _("y");
} else if (str.size() > 1 && str.GetChar(str.size() - 1) == _('s')) {
return str.substr(0, str.size() - 1);
} else {
return str;
}
}
String english_plural(const String& str) {
if (str.size() > 1 && str.GetChar(str.size() - 1) == _('y')) {
return str.substr(0, str.size() - 1) + _("ies");
} else if (str.size() > 1 && str.GetChar(str.size() - 1) == _('s')) {
return str + _("es");
} else {
return str + _("s");
}
}
// script_english_singular/plural/singplur
String do_english(String input, String(*fun)(const String&)) {
if (is_substr(input, 0, _("<param-"))) {
// a keyword parameter, of the form "<param->123</param->"
size_t start = skip_tag(input, 0);
if (start != String::npos) {
size_t end = input.find_first_of(_('<'), start);
if (end != String::npos) {
String is = input.substr(start, end - start);
return substr_replace(input, start, end, fun(is));
}
}
return input; // failed
} else {
return fun(input);
}
}
SCRIPT_FUNCTION(english_singular) {
SCRIPT_PARAM(String, input);
SCRIPT_RETURN(do_english(input, english_singular));
}
SCRIPT_FUNCTION(english_plural) {
SCRIPT_PARAM(String, input);
SCRIPT_RETURN(do_english(input, english_plural));
}
// ----------------------------------------------------------------------------- : Hints // ----------------------------------------------------------------------------- : Hints
bool is_vowel(Char c) { bool is_vowel(Char c) {
...@@ -160,6 +208,24 @@ String process_english_hints(const String& str) { ...@@ -160,6 +208,24 @@ String process_english_hints(const String& str) {
} }
ret += c; ret += c;
++i; ++i;
} else if (is_substr(str, i, _("<singular>"))) {
// singular -> keep, plural -> drop
size_t start = skip_tag(str, i);
size_t end = match_close_tag(str, start);
if (singplur == 1 && end != String::npos) {
ret += str.substr(start, end - start);
}
singplur = 0;
i = skip_tag(str, end);
} else if (is_substr(str, i, _("<plural>"))) {
// singular -> drop, plural -> keep
size_t start = skip_tag(str, i);
size_t end = match_close_tag(str, start);
if (singplur == 2 && end != String::npos) {
ret += str.substr(start, end - start);
}
singplur = 0;
i = skip_tag(str, end);
} else if (c == _('(') && singplur) { } else if (c == _('(') && singplur) {
// singular -> drop (...), plural -> keep it // singular -> drop (...), plural -> keep it
size_t end = str.find_first_of(_(')'), i); size_t end = str.find_first_of(_(')'), i);
...@@ -192,5 +258,7 @@ void init_script_english_functions(Context& ctx) { ...@@ -192,5 +258,7 @@ void init_script_english_functions(Context& ctx) {
ctx.setVariable(_("english number"), script_english_number); ctx.setVariable(_("english number"), script_english_number);
ctx.setVariable(_("english number a"), script_english_number_a); ctx.setVariable(_("english number a"), script_english_number_a);
ctx.setVariable(_("english number multiple"), script_english_number_multiple); ctx.setVariable(_("english number multiple"), script_english_number_multiple);
ctx.setVariable(_("english singular"), script_english_singular);
ctx.setVariable(_("english plural"), script_english_plural);
ctx.setVariable(_("process english hints"), script_process_english_hints); ctx.setVariable(_("process english hints"), script_process_english_hints);
} }
...@@ -132,6 +132,7 @@ void Reader::readLine(bool in_string) { ...@@ -132,6 +132,7 @@ void Reader::readLine(bool in_string) {
} }
key = cannocial_name_form(trim(key)); key = cannocial_name_form(trim(key));
value = pos == String::npos ? _("") : trim_left(line.substr(pos+1)); value = pos == String::npos ? _("") : trim_left(line.substr(pos+1));
if (key.empty() && pos!=String::npos) key = _(" "); // we don't want an empty key if there was a colon
} }
void Reader::unknownKey() { void Reader::unknownKey() {
...@@ -162,7 +163,7 @@ void Reader::unknownKey() { ...@@ -162,7 +163,7 @@ void Reader::unknownKey() {
return; return;
} }
} }
if (indent == expected_indent) { if (indent >= expected_indent) {
warning(_("Unexpected key: '") + key + _("'")); warning(_("Unexpected key: '") + key + _("'"));
do { do {
moveNext(); moveNext();
......
...@@ -386,11 +386,6 @@ String remove_tag_contents(const String& str, const String& tag) { ...@@ -386,11 +386,6 @@ String remove_tag_contents(const String& str, const String& tag) {
// ----------------------------------------------------------------------------- : Updates // ----------------------------------------------------------------------------- : Updates
/// Return all open or close tags in the given range from a string
/** for example:
* if close_tags == false, "text<tag>text</tag>text" --> "<tag>"
* if close_tags == true, "text<tag>text</tag>text" --> "</tag>"
*/
String get_tags(const String& str, size_t start, size_t end, bool open_tags, bool close_tags) { String get_tags(const String& str, size_t start, size_t end, bool open_tags, bool close_tags) {
String ret; String ret;
bool intag = false; bool intag = false;
......
...@@ -143,6 +143,13 @@ String remove_tag_contents(const String& str, const String& tag); ...@@ -143,6 +143,13 @@ String remove_tag_contents(const String& str, const String& tag);
// ----------------------------------------------------------------------------- : Updates // ----------------------------------------------------------------------------- : Updates
/// Return all open or close tags in the given range from a string
/** for example:
* if close_tags == false, "text<tag>text</tag>text" --> "<tag>"
* if close_tags == true, "text<tag>text</tag>text" --> "</tag>"
*/
String get_tags(const String& str, size_t start, size_t end, bool open_tags, bool close_tags);
/// Replace a subsection of 'input' with 'replacement'. /// Replace a subsection of 'input' with 'replacement'.
/** The section to replace is indicated by [start...end). /** The section to replace is indicated by [start...end).
* This function makes sure tags still match. It also attempts to cancel out tags. * This function makes sure tags still match. It also attempts to cancel out tags.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment