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;
|
static std::once_flag init;
|
||||||
|
|
||||||
// The peg parser rules are set up once per process, which means the GenericQuery/
|
// The peglib parser rules (and therefore their rule actions) are set up once per
|
||||||
// OracleQuery lambdas cannot capture per-instance state. The card language the
|
// process, so a rule action cannot capture per-instance state. The card language
|
||||||
// plain-text name and text queries search in is therefore kept here and applied
|
// plain-text name and text queries search in is therefore handed to the GenericQuery
|
||||||
// by those lambdas; every FilterString shares it because it reflects a single
|
// and OracleQuery rule actions through this thread-local context, which is live only
|
||||||
// global user setting.
|
// while a FilterString is being parsed. The rule actions copy it into the filter
|
||||||
static QString globalSearchLanguage;
|
// closures they produce, so card evaluation never reads process-global state.
|
||||||
static CardSearchLanguage globalSearchLanguageMode = CardSearchLanguage::English;
|
struct SearchLanguageContext
|
||||||
|
{
|
||||||
|
QString searchLanguage;
|
||||||
|
CardSearchLanguage searchLanguageMode = CardSearchLanguage::English;
|
||||||
|
};
|
||||||
|
thread_local SearchLanguageContext searchLanguageContext;
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
|
@ -365,9 +370,11 @@ static void setupParserRules()
|
||||||
|
|
||||||
search["OracleQuery"] = [](const peg::SemanticValues &sv) -> Filter {
|
search["OracleQuery"] = [](const peg::SemanticValues &sv) -> Filter {
|
||||||
const auto matcher = std::any_cast<StringMatcher>(sv[0]);
|
const auto matcher = std::any_cast<StringMatcher>(sv[0]);
|
||||||
|
const QString searchLanguage = searchLanguageContext.searchLanguage;
|
||||||
|
const CardSearchLanguage searchLanguageMode = searchLanguageContext.searchLanguageMode;
|
||||||
return [=](const CardData &x) {
|
return [=](const CardData &x) {
|
||||||
return matchesInSearchLanguage(x->getText(), x->getLocalizedText(globalSearchLanguage),
|
return matchesInSearchLanguage(x->getText(), x->getLocalizedText(searchLanguage), searchLanguage,
|
||||||
globalSearchLanguage, globalSearchLanguageMode, matcher);
|
searchLanguageMode, matcher);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -445,9 +452,11 @@ static void setupParserRules()
|
||||||
};
|
};
|
||||||
search["GenericQuery"] = [](const peg::SemanticValues &sv) -> Filter {
|
search["GenericQuery"] = [](const peg::SemanticValues &sv) -> Filter {
|
||||||
const auto matcher = std::any_cast<StringMatcher>(sv[0]);
|
const auto matcher = std::any_cast<StringMatcher>(sv[0]);
|
||||||
|
const QString searchLanguage = searchLanguageContext.searchLanguage;
|
||||||
|
const CardSearchLanguage searchLanguageMode = searchLanguageContext.searchLanguageMode;
|
||||||
return [=](const CardData &x) {
|
return [=](const CardData &x) {
|
||||||
return matchesInSearchLanguage(x->getName(), x->getLocalizedName(globalSearchLanguage),
|
return matchesInSearchLanguage(x->getName(), x->getLocalizedName(searchLanguage), searchLanguage,
|
||||||
globalSearchLanguage, globalSearchLanguageMode, matcher);
|
searchLanguageMode, matcher);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -463,7 +472,7 @@ FilterString::FilterString()
|
||||||
_error = "Not initialized";
|
_error = "Not initialized";
|
||||||
}
|
}
|
||||||
|
|
||||||
FilterString::FilterString(const QString &expr)
|
FilterString::FilterString(const QString &expr, const QString &searchLanguage, CardSearchLanguage searchLanguageMode)
|
||||||
{
|
{
|
||||||
QByteArray ba = expr.simplified().toUtf8();
|
QByteArray ba = expr.simplified().toUtf8();
|
||||||
|
|
||||||
|
|
@ -476,6 +485,8 @@ FilterString::FilterString(const QString &expr)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
searchLanguageContext = SearchLanguageContext{searchLanguage, searchLanguageMode};
|
||||||
|
|
||||||
search.set_logger([&](size_t /*ln*/, size_t col, const std::string &msg) {
|
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));
|
_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; };
|
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:
|
public:
|
||||||
FilterString();
|
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
|
[[nodiscard]] bool check(const CardData &card) const
|
||||||
{
|
{
|
||||||
if (card.isNull()) {
|
if (card.isNull()) {
|
||||||
|
|
@ -56,17 +58,6 @@ public:
|
||||||
return _error;
|
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:
|
private:
|
||||||
QString _error;
|
QString _error;
|
||||||
Filter result;
|
Filter result;
|
||||||
|
|
|
||||||
|
|
@ -239,9 +239,9 @@ void CardDatabaseDisplayModel::setFilterTree(FilterTree *_filterTree)
|
||||||
|
|
||||||
void CardDatabaseDisplayModel::setStringFilter(const QString &_src)
|
void CardDatabaseDisplayModel::setStringFilter(const QString &_src)
|
||||||
{
|
{
|
||||||
|
searchText = _src;
|
||||||
delete filterString;
|
delete filterString;
|
||||||
filterString = new FilterString(_src);
|
filterString = new FilterString(_src, searchLanguage, searchLanguageMode);
|
||||||
filterString->setSearchLanguage(searchLanguage, searchLanguageMode);
|
|
||||||
dirty();
|
dirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -255,7 +255,7 @@ void CardDatabaseDisplayModel::setSearchLanguage(const QString &searchLang, Card
|
||||||
searchLanguageMode = mode;
|
searchLanguageMode = mode;
|
||||||
|
|
||||||
if (filterString != nullptr) {
|
if (filterString != nullptr) {
|
||||||
filterString->setSearchLanguage(searchLanguage, searchLanguageMode);
|
setStringFilter(searchText);
|
||||||
}
|
}
|
||||||
dirty();
|
dirty();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ private:
|
||||||
QTimer dirtyTimer;
|
QTimer dirtyTimer;
|
||||||
QString searchLanguage;
|
QString searchLanguage;
|
||||||
CardSearchLanguage searchLanguageMode = CardSearchLanguage::English;
|
CardSearchLanguage searchLanguageMode = CardSearchLanguage::English;
|
||||||
|
QString searchText;
|
||||||
|
|
||||||
/** The translation table that will be used for sanitizeCardName. */
|
/** The translation table that will be used for sanitizeCardName. */
|
||||||
static QMap<wchar_t, wchar_t> characterTranslation;
|
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)
|
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
|
} // namespace
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue