llvm-project/llvm/unittests/ADT/StringSwitchTest.cpp
Jakub Kuderski 67696a1adc
[ADT] Reduce copies and recursion in StringSwitch (#125362)
Optimize the `.Cases` and `.CasesLower` functions to avoid needlessly
recursing on each case and copying the associated values. We can instead
take `Value` by reference and short-circuit by using the `||` operator.

Note that while the implementation uses variadic templates, we cannot
simplify the public functions in the same way. This is because the
current API forces the arguments to be converted to `StringLiterals` and
places the `Value` parameter at the very end. Even if we did some tricks
like split the parameter pack to separate out the `Value`, I do not see
how we could force conversion to `StringLiteral`.
2025-02-03 12:09:32 -05:00

233 lines
7.6 KiB
C++

//===- llvm/unittest/ADT/StringSwitchTest.cpp - StringSwitch unit tests ---===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "llvm/ADT/StringSwitch.h"
#include "gtest/gtest.h"
using namespace llvm;
TEST(StringSwitchTest, Case) {
auto Translate = [](StringRef S) {
return llvm::StringSwitch<int>(S)
.Case("0", 0)
.Case("1", 1)
.Case("2", 2)
.Case("3", 3)
.Case("4", 4)
.Case("5", 5)
.Case("6", 6)
.Case("7", 7)
.Case("8", 8)
.Case("9", 9)
.Case("A", 10)
.Case("B", 11)
.Case("C", 12)
.Case("D", 13)
.Case("E", 14)
.Case("F", 15)
.Default(-1);
};
EXPECT_EQ(1, Translate("1"));
EXPECT_EQ(2, Translate("2"));
EXPECT_EQ(11, Translate("B"));
EXPECT_EQ(-1, Translate("b"));
EXPECT_EQ(-1, Translate(""));
EXPECT_EQ(-1, Translate("Test"));
}
TEST(StringSwitchTest, CaseLower) {
auto Translate = [](StringRef S) {
return llvm::StringSwitch<int>(S)
.Case("0", 0)
.Case("1", 1)
.Case("2", 2)
.Case("3", 3)
.Case("4", 4)
.Case("5", 5)
.Case("6", 6)
.Case("7", 7)
.Case("8", 8)
.Case("9", 9)
.CaseLower("A", 10)
.CaseLower("B", 11)
.CaseLower("C", 12)
.CaseLower("D", 13)
.CaseLower("E", 14)
.CaseLower("F", 15)
.Default(-1);
};
EXPECT_EQ(1, Translate("1"));
EXPECT_EQ(2, Translate("2"));
EXPECT_EQ(11, Translate("B"));
EXPECT_EQ(11, Translate("b"));
EXPECT_EQ(-1, Translate(""));
EXPECT_EQ(-1, Translate("Test"));
}
TEST(StringSwitchTest, StartsWith) {
auto Translate = [](StringRef S) {
return llvm::StringSwitch<std::function<int(int, int)>>(S)
.StartsWith("add", [](int X, int Y) { return X + Y; })
.StartsWith("sub", [](int X, int Y) { return X - Y; })
.StartsWith("mul", [](int X, int Y) { return X * Y; })
.StartsWith("div", [](int X, int Y) { return X / Y; })
.Default([](int X, int Y) { return 0; });
};
EXPECT_EQ(15, Translate("adder")(10, 5));
EXPECT_EQ(5, Translate("subtracter")(10, 5));
EXPECT_EQ(50, Translate("multiplier")(10, 5));
EXPECT_EQ(2, Translate("divider")(10, 5));
EXPECT_EQ(0, Translate("nothing")(10, 5));
EXPECT_EQ(0, Translate("ADDER")(10, 5));
}
TEST(StringSwitchTest, StartsWithLower) {
auto Translate = [](StringRef S) {
return llvm::StringSwitch<std::function<int(int, int)>>(S)
.StartsWithLower("add", [](int X, int Y) { return X + Y; })
.StartsWithLower("sub", [](int X, int Y) { return X - Y; })
.StartsWithLower("mul", [](int X, int Y) { return X * Y; })
.StartsWithLower("div", [](int X, int Y) { return X / Y; })
.Default([](int X, int Y) { return 0; });
};
EXPECT_EQ(15, Translate("adder")(10, 5));
EXPECT_EQ(5, Translate("subtracter")(10, 5));
EXPECT_EQ(50, Translate("multiplier")(10, 5));
EXPECT_EQ(2, Translate("divider")(10, 5));
EXPECT_EQ(15, Translate("AdDeR")(10, 5));
EXPECT_EQ(5, Translate("SuBtRaCtEr")(10, 5));
EXPECT_EQ(50, Translate("MuLtIpLiEr")(10, 5));
EXPECT_EQ(2, Translate("DiViDeR")(10, 5));
EXPECT_EQ(0, Translate("nothing")(10, 5));
}
TEST(StringSwitchTest, EndsWith) {
enum class Suffix { Possible, PastTense, Process, InProgressAction, Unknown };
auto Translate = [](StringRef S) {
return llvm::StringSwitch<Suffix>(S)
.EndsWith("able", Suffix::Possible)
.EndsWith("ed", Suffix::PastTense)
.EndsWith("ation", Suffix::Process)
.EndsWith("ing", Suffix::InProgressAction)
.Default(Suffix::Unknown);
};
EXPECT_EQ(Suffix::Possible, Translate("optimizable"));
EXPECT_EQ(Suffix::PastTense, Translate("optimized"));
EXPECT_EQ(Suffix::Process, Translate("optimization"));
EXPECT_EQ(Suffix::InProgressAction, Translate("optimizing"));
EXPECT_EQ(Suffix::Unknown, Translate("optimizer"));
EXPECT_EQ(Suffix::Unknown, Translate("OPTIMIZABLE"));
}
TEST(StringSwitchTest, EndsWithLower) {
enum class Suffix { Possible, PastTense, Process, InProgressAction, Unknown };
auto Translate = [](StringRef S) {
return llvm::StringSwitch<Suffix>(S)
.EndsWithLower("able", Suffix::Possible)
.EndsWithLower("ed", Suffix::PastTense)
.EndsWithLower("ation", Suffix::Process)
.EndsWithLower("ing", Suffix::InProgressAction)
.Default(Suffix::Unknown);
};
EXPECT_EQ(Suffix::Possible, Translate("optimizable"));
EXPECT_EQ(Suffix::Possible, Translate("OPTIMIZABLE"));
EXPECT_EQ(Suffix::PastTense, Translate("optimized"));
EXPECT_EQ(Suffix::Process, Translate("optimization"));
EXPECT_EQ(Suffix::InProgressAction, Translate("optimizing"));
EXPECT_EQ(Suffix::Unknown, Translate("optimizer"));
}
TEST(StringSwitchTest, Cases) {
enum class OSType { Windows, Linux, Unknown };
auto Translate = [](StringRef S) {
return llvm::StringSwitch<OSType>(S)
.Cases(StringLiteral::withInnerNUL("wind\0ws"), "win32", "winnt",
OSType::Windows)
.Cases("linux", "unix", "*nix", "posix", OSType::Linux)
.Default(OSType::Unknown);
};
EXPECT_EQ(OSType::Windows, Translate(llvm::StringRef("wind\0ws", 7)));
EXPECT_EQ(OSType::Windows, Translate("win32"));
EXPECT_EQ(OSType::Windows, Translate("winnt"));
EXPECT_EQ(OSType::Linux, Translate("linux"));
EXPECT_EQ(OSType::Linux, Translate("unix"));
EXPECT_EQ(OSType::Linux, Translate("*nix"));
EXPECT_EQ(OSType::Linux, Translate("posix"));
// Note that the whole null-terminator embedded string is required for the
// case to match.
EXPECT_EQ(OSType::Unknown, Translate("wind"));
EXPECT_EQ(OSType::Unknown, Translate("Windows"));
EXPECT_EQ(OSType::Unknown, Translate(""));
}
TEST(StringSwitchTest, CasesLower) {
enum class OSType { Windows, Linux, Unknown };
auto Translate = [](StringRef S) {
return llvm::StringSwitch<OSType>(S)
.CasesLower(StringLiteral::withInnerNUL("wind\0ws"), "win32", "winnt",
OSType::Windows)
.CasesLower("linux", "unix", "*nix", "posix", OSType::Linux)
.Default(OSType::Unknown);
};
EXPECT_EQ(OSType::Windows, Translate(llvm::StringRef("WIND\0WS", 7)));
EXPECT_EQ(OSType::Windows, Translate("WIN32"));
EXPECT_EQ(OSType::Windows, Translate("WINNT"));
EXPECT_EQ(OSType::Linux, Translate("LINUX"));
EXPECT_EQ(OSType::Linux, Translate("UNIX"));
EXPECT_EQ(OSType::Linux, Translate("*NIX"));
EXPECT_EQ(OSType::Linux, Translate("POSIX"));
EXPECT_EQ(OSType::Windows, Translate(llvm::StringRef("wind\0ws", 7)));
EXPECT_EQ(OSType::Linux, Translate("linux"));
EXPECT_EQ(OSType::Unknown, Translate("wind"));
EXPECT_EQ(OSType::Unknown, Translate(""));
}
TEST(StringSwitchTest, CasesCopies) {
struct Copyable {
unsigned &NumCopies;
Copyable(unsigned &Value) : NumCopies(Value) {}
Copyable(const Copyable &Other) : NumCopies(Other.NumCopies) {
++NumCopies;
}
Copyable &operator=(const Copyable &Other) {
++NumCopies;
return *this;
}
};
// Check that evaluating multiple cases does not cause unnecessary copies.
unsigned NumCopies = 0;
llvm::StringSwitch<Copyable, void>("baz").Cases("foo", "bar", "baz", "qux",
Copyable{NumCopies});
EXPECT_EQ(NumCopies, 1u);
NumCopies = 0;
llvm::StringSwitch<Copyable, void>("baz").CasesLower(
"Foo", "Bar", "Baz", "Qux", Copyable{NumCopies});
EXPECT_EQ(NumCopies, 1u);
}