diff --git a/libcockatrice_filters/libcockatrice/filters/filter_string.cpp b/libcockatrice_filters/libcockatrice/filters/filter_string.cpp index aaf391c03..f08004856 100644 --- a/libcockatrice_filters/libcockatrice/filters/filter_string.cpp +++ b/libcockatrice_filters/libcockatrice/filters/filter_string.cpp @@ -65,7 +65,7 @@ RegexMatcherString <- ('\\/' / !'/' .)+ FlexStringValue <- CompactStringSet / String / [(] StringList [)] CompactStringSet <- StringListString ([,+] StringListString)+ -SetExpression <- NumericOperator ws? String +SetExpression <- ws? String NumericExpression <- NumericOperator ws? NumericValue NumericOperator <- [=:] / <[> @@ -114,16 +114,8 @@ static void setupParserRules() }; } - auto matcher = std::any_cast(sv[0]); - return [=](const CardData &x) -> bool { - const auto &sets = x->getSets().values(); - auto matchesSet = [&](const PrintingInfo &printing) { - return printing.getSet()->getEnabled() && matcher(printing.getSet()->getReleaseDate().toJulianDay()); - }; - return std::any_of(sets.begin(), sets.end(), [&](const auto &printings) { - return std::any_of(printings.begin(), printings.end(), matchesSet); - }); - }; + auto matcher = std::any_cast(sv[0]); + return matcher; }; search["Rarity"] = [](const peg::SemanticValues &sv) -> QString { switch (tolower(std::string(sv.sv())[0])) { @@ -300,17 +292,57 @@ static void setupParserRules() return [=](int s) { return comparer(s, arg); }; }; - search["SetExpression"] = [](const peg::SemanticValues &sv) -> NumberMatcher { + search["SetExpression"] = [](const peg::SemanticValues &sv) -> Filter { const auto comparer = std::any_cast(sv[0]); const auto setCode = std::any_cast(sv[1]); + const auto operatorText = QString::fromStdString(std::string(sv.token(0))); + + auto printingsOf = [](const CardData &x) { + QList printings; + for (const auto &printingsPerSet : x->getSets()) { + printings.append(printingsPerSet); + } + return printings; + }; + auto isExactSet = [setCode](const PrintingInfo &printing) { + return printing.getSet()->getEnabled() && + printing.getSet()->getShortName().compare(setCode, Qt::CaseInsensitive) == 0; + }; + + if (operatorText == ":" || operatorText == "=") { + return [=](const CardData &x) { + const auto printings = printingsOf(x); + return std::any_of(printings.begin(), printings.end(), isExactSet); + }; + } + if (operatorText == "!" || operatorText == "!=") { + return [=](const CardData &x) { + const auto printings = printingsOf(x); + return std::none_of(printings.begin(), printings.end(), isExactSet); + }; + } + const auto allSets = CardDatabaseManager::getInstance()->getSetList(); - for (auto &set : allSets) { - if (set->getShortName() == setCode) { - const int releaseDate = set->getReleaseDate().toJulianDay(); - return [=](int s) { return comparer(s, releaseDate); }; + int releaseDate = 0; + bool setFound = false; + for (const auto &set : allSets) { + if (set->getShortName().compare(setCode, Qt::CaseInsensitive) == 0) { + releaseDate = set->getReleaseDate().toJulianDay(); + setFound = true; + break; } } - return [](int) { return false; }; + if (!setFound) { + return [](const CardData &) { return false; }; + } + auto matchesReleaseDate = [comparer, releaseDate](const PrintingInfo &printing) { + return printing.getSet()->getEnabled() && + comparer(printing.getSet()->getReleaseDate().toJulianDay(), releaseDate); + }; + return [=](const CardData &x) { + const auto printings = printingsOf(x); + return std::any_of(printings.begin(), printings.end(), matchesReleaseDate); + }; }; search["NormalMatcher"] = [](const peg::SemanticValues &sv) -> StringMatcher { diff --git a/tests/carddatabase/filter_string_test.cpp b/tests/carddatabase/filter_string_test.cpp index c6d68be1f..56f33825c 100644 --- a/tests/carddatabase/filter_string_test.cpp +++ b/tests/carddatabase/filter_string_test.cpp @@ -2,6 +2,8 @@ #include "test_card_database_path_provider.h" #include "gtest/gtest.h" +#include +#include #include #include #include @@ -71,10 +73,71 @@ QUERY(Color2, cat, "c:gw", true) QUERY(Color3, cat, "c!g", true) QUERY(Color4, cat, "c!gw", false) +QUERY(SetCodeCaseInsensitive1, cat, "set:cat", true) +QUERY(SetCodeCaseInsensitive2, cat, "set:CAT", true) +QUERY(SetCodeCaseInsensitive3, cat, "set:CAt", true) +QUERY(SetCodeShortForm, cat, "e:cat", true) +QUERY(SetCodeWrongSet, cat, "set:who", false) +QUERY(SetCodeWrongSet2, doctor, "set:cat", false) + QUERY(BracketNextToUnquotedString, cat, "(o:woof OR o:meow)", true) } // namespace +class SetQuery : public ::testing::Test +{ +protected: + void SetUp() override + { + // EOE and EOC share a release date, like a set and its Commander counterpart. + const QDate sharedReleaseDate(2026, 3, 13); + mainSet = CardSet::newInstance(&controller, "EOE", "Edge of Eternities", "expansion", sharedReleaseDate, + CardSet::PriorityPrimary); + commanderSet = CardSet::newInstance(&controller, "EOC", "Edge of Eternities Commander", "commander", + sharedReleaseDate, CardSet::PrioritySecondary); + + inBothSets = newCardWithPrintings({{"EOE", mainSet}, {"EOC", commanderSet}}); + onlyInCommanderSet = newCardWithPrintings({{"EOC", commanderSet}}); + } + + CardInfoPtr newCardWithPrintings(const QList> &printings) + { + SetToPrintingsMap setsInfo; + for (const auto &printing : printings) { + setsInfo[printing.first].append(PrintingInfo(printing.second)); + } + return CardInfo::newInstance("Test Card", "", false, {}, {}, {}, setsInfo, CardInfo::UiAttributes()); + } + + NoopCardSetPriorityController controller; + CardSetPtr mainSet; + CardSetPtr commanderSet; + CardInfoPtr inBothSets; + CardInfoPtr onlyInCommanderSet; +}; + +TEST_F(SetQuery, ExactMatchSeparatesSameDaySets) +{ + EXPECT_TRUE(FilterString("set:EOE").check(inBothSets)); + EXPECT_TRUE(FilterString("e:EOE").check(inBothSets)); + EXPECT_TRUE(FilterString("set:EOC").check(inBothSets)); + EXPECT_FALSE(FilterString("set:EOE").check(onlyInCommanderSet)); + EXPECT_TRUE(FilterString("set:EOC").check(onlyInCommanderSet)); +} + +TEST_F(SetQuery, ExactMatchIsCaseInsensitive) +{ + EXPECT_TRUE(FilterString("set:eoe").check(inBothSets)); + EXPECT_TRUE(FilterString("set:EoE").check(inBothSets)); + EXPECT_FALSE(FilterString("set:eoe").check(onlyInCommanderSet)); +} + +TEST_F(SetQuery, NotEqualsMatchesPrintingsOutsideTheSet) +{ + EXPECT_FALSE(FilterString("set!EOE").check(inBothSets)); + EXPECT_TRUE(FilterString("set!EOE").check(onlyInCommanderSet)); +} + int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv);