mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-09-21 09:05:10 -07:00
[Card] Bind search language per FilterString instance
The peg parser rules are set up once per process, so the GenericQuery and OracleQuery rule actions could not capture per-instance state. Instead of storing the search language in a process-global that FilterString instance methods mutate, hand it to the rule actions through a thread-local parse context and copy it into the filter closures they produce. Card evaluation in FilterString::check no longer reads any process-global state, and each instance keeps the language it was built with; constructing one instance no longer changes what unrelated instances (deck filter, drop-to-hand, zone views) match against. The card database display model stores the raw query and rebuilds the FilterString when the search language changes, since the language is now bound at parse time. Add tests for the English/Selected/Both search modes, the English fallback for untranslated cards, and per-instance language independence.
This commit is contained in:
parent
e029173e29
commit
14cf4ee2a9
5 changed files with 81 additions and 33 deletions
|
|
@ -74,13 +74,18 @@ NumericValue <- [0-9]+
|
|||
|
||||
static std::once_flag init;
|
||||
|
||||
// The peg parser rules are set up once per process, which means the GenericQuery/
|
||||
// OracleQuery lambdas cannot capture per-instance state. The card language the
|
||||
// plain-text name and text queries search in is therefore kept here and applied
|
||||
// by those lambdas; every FilterString shares it because it reflects a single
|
||||
// global user setting.
|
||||
static QString globalSearchLanguage;
|
||||
static CardSearchLanguage globalSearchLanguageMode = CardSearchLanguage::English;
|
||||
// The peglib parser rules (and therefore their rule actions) are set up once per
|
||||
// process, so a rule action cannot capture per-instance state. The card language
|
||||
// plain-text name and text queries search in is therefore handed to the GenericQuery
|
||||
// and OracleQuery rule actions through this thread-local context, which is live only
|
||||
// while a FilterString is being parsed. The rule actions copy it into the filter
|
||||
// closures they produce, so card evaluation never reads process-global state.
|
||||
struct SearchLanguageContext
|
||||
{
|
||||
QString searchLanguage;
|
||||
CardSearchLanguage searchLanguageMode = CardSearchLanguage::English;
|
||||
};
|
||||
thread_local SearchLanguageContext searchLanguageContext;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
|
@ -365,9 +370,11 @@ static void setupParserRules()
|
|||
|
||||
search["OracleQuery"] = [](const peg::SemanticValues &sv) -> Filter {
|
||||
const auto matcher = std::any_cast<StringMatcher>(sv[0]);
|
||||
const QString searchLanguage = searchLanguageContext.searchLanguage;
|
||||
const CardSearchLanguage searchLanguageMode = searchLanguageContext.searchLanguageMode;
|
||||
return [=](const CardData &x) {
|
||||
return matchesInSearchLanguage(x->getText(), x->getLocalizedText(globalSearchLanguage),
|
||||
globalSearchLanguage, globalSearchLanguageMode, matcher);
|
||||
return matchesInSearchLanguage(x->getText(), x->getLocalizedText(searchLanguage), searchLanguage,
|
||||
searchLanguageMode, matcher);
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -445,9 +452,11 @@ static void setupParserRules()
|
|||
};
|
||||
search["GenericQuery"] = [](const peg::SemanticValues &sv) -> Filter {
|
||||
const auto matcher = std::any_cast<StringMatcher>(sv[0]);
|
||||
const QString searchLanguage = searchLanguageContext.searchLanguage;
|
||||
const CardSearchLanguage searchLanguageMode = searchLanguageContext.searchLanguageMode;
|
||||
return [=](const CardData &x) {
|
||||
return matchesInSearchLanguage(x->getName(), x->getLocalizedName(globalSearchLanguage),
|
||||
globalSearchLanguage, globalSearchLanguageMode, matcher);
|
||||
return matchesInSearchLanguage(x->getName(), x->getLocalizedName(searchLanguage), searchLanguage,
|
||||
searchLanguageMode, matcher);
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -463,7 +472,7 @@ FilterString::FilterString()
|
|||
_error = "Not initialized";
|
||||
}
|
||||
|
||||
FilterString::FilterString(const QString &expr)
|
||||
FilterString::FilterString(const QString &expr, const QString &searchLanguage, CardSearchLanguage searchLanguageMode)
|
||||
{
|
||||
QByteArray ba = expr.simplified().toUtf8();
|
||||
|
||||
|
|
@ -476,6 +485,8 @@ FilterString::FilterString(const QString &expr)
|
|||
return;
|
||||
}
|
||||
|
||||
searchLanguageContext = SearchLanguageContext{searchLanguage, searchLanguageMode};
|
||||
|
||||
search.set_logger([&](size_t /*ln*/, size_t col, const std::string &msg) {
|
||||
_error = QString("Error at position %1: %2").arg(col).arg(QString::fromStdString(msg));
|
||||
});
|
||||
|
|
@ -485,9 +496,3 @@ FilterString::FilterString(const QString &expr)
|
|||
result = [](const CardData &) -> bool { return false; };
|
||||
}
|
||||
}
|
||||
|
||||
void FilterString::setSearchLanguage(const QString &searchLanguage, CardSearchLanguage searchLanguageMode)
|
||||
{
|
||||
globalSearchLanguage = searchLanguage;
|
||||
globalSearchLanguageMode = searchLanguageMode;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,9 @@ class FilterString
|
|||
{
|
||||
public:
|
||||
FilterString();
|
||||
explicit FilterString(const QString &exp);
|
||||
explicit FilterString(const QString &exp,
|
||||
const QString &searchLanguage = QString(),
|
||||
CardSearchLanguage searchLanguageMode = CardSearchLanguage::English);
|
||||
[[nodiscard]] bool check(const CardData &card) const
|
||||
{
|
||||
if (card.isNull()) {
|
||||
|
|
@ -56,17 +58,6 @@ public:
|
|||
return _error;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the card language plain-text name and text queries run against.
|
||||
*
|
||||
* The peg parser rules are set up once per process, so this propagates to
|
||||
* every FilterString instance created through the shared parser.
|
||||
*
|
||||
* @param searchLanguage Empty string for English only, otherwise the card language code.
|
||||
* @param searchLanguageMode The search language mode from CardSearchLanguage.
|
||||
*/
|
||||
void setSearchLanguage(const QString &searchLanguage, CardSearchLanguage searchLanguageMode);
|
||||
|
||||
private:
|
||||
QString _error;
|
||||
Filter result;
|
||||
|
|
|
|||
|
|
@ -239,9 +239,9 @@ void CardDatabaseDisplayModel::setFilterTree(FilterTree *_filterTree)
|
|||
|
||||
void CardDatabaseDisplayModel::setStringFilter(const QString &_src)
|
||||
{
|
||||
searchText = _src;
|
||||
delete filterString;
|
||||
filterString = new FilterString(_src);
|
||||
filterString->setSearchLanguage(searchLanguage, searchLanguageMode);
|
||||
filterString = new FilterString(_src, searchLanguage, searchLanguageMode);
|
||||
dirty();
|
||||
}
|
||||
|
||||
|
|
@ -255,7 +255,7 @@ void CardDatabaseDisplayModel::setSearchLanguage(const QString &searchLang, Card
|
|||
searchLanguageMode = mode;
|
||||
|
||||
if (filterString != nullptr) {
|
||||
filterString->setSearchLanguage(searchLanguage, searchLanguageMode);
|
||||
setStringFilter(searchText);
|
||||
}
|
||||
dirty();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ private:
|
|||
QTimer dirtyTimer;
|
||||
QString searchLanguage;
|
||||
CardSearchLanguage searchLanguageMode = CardSearchLanguage::English;
|
||||
QString searchText;
|
||||
|
||||
/** The translation table that will be used for sanitizeCardName. */
|
||||
static QMap<wchar_t, wchar_t> characterTranslation;
|
||||
|
|
|
|||
|
|
@ -73,6 +73,57 @@ QUERY(Color4, cat, "c!gw", false)
|
|||
|
||||
QUERY(BracketNextToUnquotedString, cat, "(o:woof OR o:meow)", true)
|
||||
|
||||
CardInfoPtr localizedCat()
|
||||
{
|
||||
CardInfoPtr localized = CardInfo::newInstance("Cat", "Meow!", false, {}, {}, {}, {}, {});
|
||||
localized->setLocalizedName("de", "Kater");
|
||||
localized->setLocalizedText("de", "miaut");
|
||||
return localized;
|
||||
}
|
||||
|
||||
TEST_F(CardQuery, SearchLanguageEnglishMatchesOnlyEnglish)
|
||||
{
|
||||
const CardData localized = localizedCat();
|
||||
ASSERT_TRUE(FilterString("Cat", "de", CardSearchLanguage::English).check(localized));
|
||||
ASSERT_FALSE(FilterString("Kater", "de", CardSearchLanguage::English).check(localized));
|
||||
}
|
||||
|
||||
TEST_F(CardQuery, SearchLanguageSelectedMatchesLocalizedNameAndText)
|
||||
{
|
||||
const CardData localized = localizedCat();
|
||||
ASSERT_TRUE(FilterString("Kater", "de", CardSearchLanguage::Selected).check(localized));
|
||||
ASSERT_TRUE(FilterString("o:miaut", "de", CardSearchLanguage::Selected).check(localized));
|
||||
ASSERT_FALSE(FilterString("Cat", "de", CardSearchLanguage::Selected).check(localized));
|
||||
}
|
||||
|
||||
TEST_F(CardQuery, SearchLanguageSelectedFallsBackToEnglishForUntranslatedCards)
|
||||
{
|
||||
const CardData localized = localizedCat();
|
||||
ASSERT_TRUE(FilterString("Cat", "fr", CardSearchLanguage::Selected).check(localized));
|
||||
ASSERT_FALSE(FilterString("Kater", "fr", CardSearchLanguage::Selected).check(localized));
|
||||
}
|
||||
|
||||
TEST_F(CardQuery, SearchLanguageBothMatchesEitherLanguage)
|
||||
{
|
||||
const CardData localized = localizedCat();
|
||||
ASSERT_TRUE(FilterString("Cat", "de", CardSearchLanguage::Both).check(localized));
|
||||
ASSERT_TRUE(FilterString("Kater", "de", CardSearchLanguage::Both).check(localized));
|
||||
}
|
||||
|
||||
TEST_F(CardQuery, SearchLanguageIsBoundPerInstance)
|
||||
{
|
||||
const CardData localized = localizedCat();
|
||||
|
||||
FilterString germanQuery("Kater", "de", CardSearchLanguage::Selected);
|
||||
ASSERT_TRUE(germanQuery.check(localized));
|
||||
|
||||
// Constructing an English-bound instance afterwards must not change the
|
||||
// language the earlier instance searches in.
|
||||
FilterString englishQuery("Kater", "", CardSearchLanguage::English);
|
||||
ASSERT_FALSE(englishQuery.check(localized));
|
||||
ASSERT_TRUE(germanQuery.check(localized));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char **argv)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue