diff --git a/src/tint/ast/transform/renamer_test.cc b/src/tint/ast/transform/renamer_test.cc index 4e97c6f112..c93340d1ba 100644 --- a/src/tint/ast/transform/renamer_test.cc +++ b/src/tint/ast/transform/renamer_test.cc @@ -22,6 +22,7 @@ #include "src/tint/ast/transform/test_helper.h" #include "src/tint/builtin/builtin.h" #include "src/tint/builtin/texel_format.h" +#include "src/tint/utils/string.h" namespace tint::ast::transform { namespace { diff --git a/src/tint/bench/benchmark.cc b/src/tint/bench/benchmark.cc index 4804a712bf..115d0b2a00 100644 --- a/src/tint/bench/benchmark.cc +++ b/src/tint/bench/benchmark.cc @@ -19,6 +19,7 @@ #include #include +#include "src/tint/utils/string.h" #include "src/tint/utils/string_stream.h" namespace tint::bench { diff --git a/src/tint/resolver/dependency_graph.cc b/src/tint/resolver/dependency_graph.cc index 5ba9ee2da0..504e8ac566 100644 --- a/src/tint/resolver/dependency_graph.cc +++ b/src/tint/resolver/dependency_graph.cc @@ -66,6 +66,7 @@ #include "src/tint/utils/defer.h" #include "src/tint/utils/map.h" #include "src/tint/utils/scoped_assignment.h" +#include "src/tint/utils/string.h" #include "src/tint/utils/string_stream.h" #include "src/tint/utils/unique_vector.h" diff --git a/src/tint/resolver/resolver.cc b/src/tint/resolver/resolver.cc index 09d8464ffe..fb0643531b 100644 --- a/src/tint/resolver/resolver.cc +++ b/src/tint/resolver/resolver.cc @@ -3761,8 +3761,9 @@ bool Resolver::DiagnosticControl(const ast::DiagnosticControl& control) { } else { utils::StringStream ss; ss << "unrecognized diagnostic rule 'chromium." << name << "'\n"; - utils::SuggestAlternatives(name, builtin::kChromiumDiagnosticRuleStrings, ss, - "chromium."); + utils::SuggestAlternativeOptions opts; + opts.prefix = "chromium."; + utils::SuggestAlternatives(name, builtin::kChromiumDiagnosticRuleStrings, ss, opts); AddWarning(ss.str(), control.rule_name->source); } } diff --git a/src/tint/utils/string.cc b/src/tint/utils/string.cc index 67eaf2bc09..00b51a8a89 100644 --- a/src/tint/utils/string.cc +++ b/src/tint/utils/string.cc @@ -15,6 +15,7 @@ #include #include "src/tint/utils/string.h" +#include "src/tint/utils/transform.h" #include "src/tint/utils/vector.h" namespace tint::utils { @@ -51,33 +52,46 @@ size_t Distance(std::string_view str_a, std::string_view str_b) { void SuggestAlternatives(std::string_view got, Slice strings, utils::StringStream& ss, - std::string_view prefix /* = "" */) { + const SuggestAlternativeOptions& options /* = {} */) { + auto views = Transform<8>(strings, [](char const* const str) { return std::string_view(str); }); + SuggestAlternatives(got, views.Slice(), ss, options); +} + +void SuggestAlternatives(std::string_view got, + Slice strings, + utils::StringStream& ss, + const SuggestAlternativeOptions& options /* = {} */) { // If the string typed was within kSuggestionDistance of one of the possible enum values, // suggest that. Don't bother with suggestions if the string was extremely long. constexpr size_t kSuggestionDistance = 5; constexpr size_t kSuggestionMaxLength = 64; if (!got.empty() && got.size() < kSuggestionMaxLength) { size_t candidate_dist = kSuggestionDistance; - const char* candidate = nullptr; - for (auto* str : strings) { + std::string_view candidate; + for (auto str : strings) { auto dist = utils::Distance(str, got); if (dist < candidate_dist) { candidate = str; candidate_dist = dist; } } - if (candidate) { - ss << "Did you mean '" << prefix << candidate << "'?\n"; + if (!candidate.empty()) { + ss << "Did you mean '" << options.prefix << candidate << "'?"; + if (options.list_possible_values) { + ss << "\n"; + } } } - // List all the possible enumerator values - ss << "Possible values: "; - for (auto* str : strings) { - if (str != strings[0]) { - ss << ", "; + if (options.list_possible_values) { + // List all the possible enumerator values + ss << "Possible values: "; + for (auto str : strings) { + if (str != strings[0]) { + ss << ", "; + } + ss << "'" << options.prefix << str << "'"; } - ss << "'" << prefix << str << "'"; } } diff --git a/src/tint/utils/string.h b/src/tint/utils/string.h index e7313653c3..99c930a709 100644 --- a/src/tint/utils/string.h +++ b/src/tint/utils/string.h @@ -20,6 +20,7 @@ #include "src/tint/utils/slice.h" #include "src/tint/utils/string_stream.h" +#include "src/tint/utils/vector.h" namespace tint::utils { @@ -38,6 +39,12 @@ namespace tint::utils { return str; } +/// @param value the boolean value to be printed as a string +/// @returns value printed as a string via the stream `<<` operator +inline std::string ToString(bool value) { + return value ? "true" : "false"; +} + /// @param value the value to be printed as a string /// @returns value printed as a string via the stream `<<` operator template @@ -75,15 +82,33 @@ inline size_t HasSuffix(std::string_view str, std::string_view suffix) { /// @returns the Levenshtein distance between @p a and @p b size_t Distance(std::string_view a, std::string_view b); +/// Options for SuggestAlternatives() +struct SuggestAlternativeOptions { + /// The prefix to apply to the strings when printing + std::string_view prefix; + /// List all the possible values + bool list_possible_values = true; +}; + /// Suggest alternatives for an unrecognized string from a list of possible values. /// @param got the unrecognized string /// @param strings the list of possible values /// @param ss the stream to write the suggest and list of possible values to -/// @param prefix the prefix to apply to the strings when printing (optional) +/// @param options options for the suggestion void SuggestAlternatives(std::string_view got, Slice strings, utils::StringStream& ss, - std::string_view prefix = ""); + const SuggestAlternativeOptions& options = {}); + +/// Suggest alternatives for an unrecognized string from a list of possible values. +/// @param got the unrecognized string +/// @param strings the list of possible values +/// @param ss the stream to write the suggest and list of possible values to +/// @param options options for the suggestion +void SuggestAlternatives(std::string_view got, + Slice strings, + utils::StringStream& ss, + const SuggestAlternativeOptions& options = {}); /// @param str the input string /// @param pred the predicate function @@ -150,6 +175,51 @@ inline std::string_view TrimSpace(std::string_view str) { return Trim(str, IsSpace); } +/// @param str the input string +/// @param delimiter the delimiter +/// @return @p str split at each occurrence of @p delimiter +inline utils::Vector Split(std::string_view str, std::string_view delimiter) { + utils::Vector out; + while (str.length() > delimiter.length()) { + auto pos = str.find(delimiter); + if (pos == std::string_view::npos) { + break; + } + out.Push(str.substr(0, pos)); + str = str.substr(pos + delimiter.length()); + } + out.Push(str); + return out; +} + +/// @returns @p str quoted with ' +inline std::string Quote(std::string_view str) { + return "'" + std::string(str) + "'"; +} + +/// @param parts the input parts +/// @param delimiter the delimiter +/// @return @p parts joined as a string, delimited with @p delimiter +template +inline std::string Join(utils::VectorRef parts, std::string_view delimiter) { + utils::StringStream s; + for (auto& part : parts) { + if (part != parts.Front()) { + s << delimiter; + } + s << part; + } + return s.str(); +} + +/// @param parts the input parts +/// @param delimiter the delimiter +/// @return @p parts joined as a string, delimited with @p delimiter +template +inline std::string Join(const utils::Vector& parts, std::string_view delimiter) { + return Join(utils::VectorRef(parts), delimiter); +} + } // namespace tint::utils #endif // SRC_TINT_UTILS_STRING_H_ diff --git a/src/tint/utils/string_stream.h b/src/tint/utils/string_stream.h index 7cbce090e6..ecb88f7ad2 100644 --- a/src/tint/utils/string_stream.h +++ b/src/tint/utils/string_stream.h @@ -24,6 +24,7 @@ #include #include "src/tint/utils/unicode.h" +#include "src/tint/utils/vector.h" namespace tint::utils { @@ -189,6 +190,44 @@ class StringStream { /// @returns out so calls can be chained utils::StringStream& operator<<(utils::StringStream& out, CodePoint codepoint); +/// Prints the vector @p vec to @p o +/// @param o the stream to write to +/// @param vec the vector +/// @return the stream so calls can be chained +template +inline utils::StringStream& operator<<(utils::StringStream& o, const utils::Vector& vec) { + o << "["; + bool first = true; + for (auto& el : vec) { + if (!first) { + o << ", "; + } + first = false; + o << el; + } + o << "]"; + return o; +} + +/// Prints the vector @p vec to @p o +/// @param o the stream to write to +/// @param vec the vector reference +/// @return the stream so calls can be chained +template +inline utils::StringStream& operator<<(utils::StringStream& o, utils::VectorRef vec) { + o << "["; + bool first = true; + for (auto& el : vec) { + if (!first) { + o << ", "; + } + first = false; + o << el; + } + o << "]"; + return o; +} + } // namespace tint::utils #endif // SRC_TINT_UTILS_STRING_STREAM_H_ diff --git a/src/tint/utils/string_test.cc b/src/tint/utils/string_test.cc index 9cf8370a5d..676c3418b6 100644 --- a/src/tint/utils/string_test.cc +++ b/src/tint/utils/string_test.cc @@ -14,7 +14,7 @@ #include "src/tint/utils/string.h" -#include "gtest/gtest.h" +#include "gmock/gmock.h" #include "src/tint/utils/string_stream.h" namespace tint::utils { @@ -34,6 +34,8 @@ TEST(StringTest, ReplaceAll) { } TEST(StringTest, ToString) { + EXPECT_EQ("true", ToString(true)); + EXPECT_EQ("false", ToString(false)); EXPECT_EQ("123", ToString(123)); EXPECT_EQ("hello", ToString("hello")); } @@ -82,6 +84,23 @@ Possible values: 'hello world', 'Hello World')"); SuggestAlternatives("hello world", alternatives, ss); EXPECT_EQ(ss.str(), R"(Possible values: 'foobar', 'something else')"); } + { + const char* alternatives[] = {"hello world", "Hello World"}; + utils::StringStream ss; + SuggestAlternativeOptions opts; + opts.prefix = "$"; + SuggestAlternatives("hello wordl", alternatives, ss, opts); + EXPECT_EQ(ss.str(), R"(Did you mean '$hello world'? +Possible values: '$hello world', '$Hello World')"); + } + { + const char* alternatives[] = {"hello world", "Hello World"}; + utils::StringStream ss; + SuggestAlternativeOptions opts; + opts.list_possible_values = false; + SuggestAlternatives("hello world", alternatives, ss, opts); + EXPECT_EQ(ss.str(), R"(Did you mean 'hello world'?)"); + } } TEST(StringTest, TrimLeft) { @@ -153,5 +172,27 @@ TEST(StringTest, TrimSpace) { EXPECT_EQ(TrimSpace(""), ""); } +TEST(StringTest, Quote) { + EXPECT_EQ("'meow'", Quote("meow")); +} + +#if 0 // Enable when moved to C++20 (https://github.com/google/googletest/issues/3081) +TEST(StringTest, Split) { + EXPECT_THAT(Split("", ","), testing::ElementsAre("")); + EXPECT_THAT(Split("cat", ","), testing::ElementsAre("cat")); + EXPECT_THAT(Split("cat,", ","), testing::ElementsAre("cat", "")); + EXPECT_THAT(Split(",cat", ","), testing::ElementsAre("", "cat")); + EXPECT_THAT(Split("cat,dog,fish", ","), testing::ElementsAre("cat", "dog", "fish")); + EXPECT_THAT(Split("catdogfish", "dog"), testing::ElementsAre("cat", "fish")); +} +#endif + +TEST(StringTest, Join) { + EXPECT_EQ(Join(utils::Vector{}, ","), ""); + EXPECT_EQ(Join(utils::Vector{1, 2, 3}, ","), "1,2,3"); + EXPECT_EQ(Join(utils::Vector{"cat"}, ","), "cat"); + EXPECT_EQ(Join(utils::Vector{"cat", "dog"}, ","), "cat,dog"); +} + } // namespace } // namespace tint::utils diff --git a/src/tint/utils/transform.h b/src/tint/utils/transform.h index 9615471c5c..7ecd941a40 100644 --- a/src/tint/utils/transform.h +++ b/src/tint/utils/transform.h @@ -85,6 +85,36 @@ auto Transform(const Vector& in, TRANSFORMER&& transform) return result; } +/// Transform performs an element-wise transformation of a slice. +/// @param in the input slice. +/// @param transform the transformation function with signature: `OUT(IN)` +/// @tparam N the small-array size of the returned Vector +/// @returns a new vector with each element of the source vector transformed by `transform`. +template +auto Transform(Slice in, TRANSFORMER&& transform) -> Vector { + Vector result; + result.Reserve(in.len); + for (size_t i = 0; i < in.len; ++i) { + result.Push(transform(in[i])); + } + return result; +} + +/// Transform performs an element-wise transformation of a slice. +/// @param in the input slice. +/// @param transform the transformation function with signature: `OUT(IN, size_t)` +/// @tparam N the small-array size of the returned Vector +/// @returns a new vector with each element of the source vector transformed by `transform`. +template +auto Transform(Slice in, TRANSFORMER&& transform) -> Vector { + Vector result; + result.Reserve(in.len); + for (size_t i = 0; i < in.len; ++i) { + result.Push(transform(in[i], i)); + } + return result; +} + /// Transform performs an element-wise transformation of a vector reference. /// @param in the input vector. /// @param transform the transformation function with signature: `OUT(IN)` @@ -92,13 +122,7 @@ auto Transform(const Vector& in, TRANSFORMER&& transform) /// @returns a new vector with each element of the source vector transformed by `transform`. template auto Transform(VectorRef in, TRANSFORMER&& transform) -> Vector { - const auto count = in.Length(); - Vector result; - result.Reserve(count); - for (size_t i = 0; i < count; ++i) { - result.Push(transform(in[i])); - } - return result; + return Transform(in.Slice(), std::forward(transform)); } /// Transform performs an element-wise transformation of a vector reference. @@ -109,13 +133,7 @@ auto Transform(VectorRef in, TRANSFORMER&& transform) -> Vector auto Transform(VectorRef in, TRANSFORMER&& transform) -> Vector { - const auto count = in.Length(); - Vector result; - result.Reserve(count); - for (size_t i = 0; i < count; ++i) { - result.Push(transform(in[i], i)); - } - return result; + return Transform(in.Slice(), std::forward(transform)); } /// TransformN performs an element-wise transformation of a vector, transforming and returning at diff --git a/src/tint/utils/vector.h b/src/tint/utils/vector.h index 65595e7920..acb0e39779 100644 --- a/src/tint/utils/vector.h +++ b/src/tint/utils/vector.h @@ -26,8 +26,6 @@ #include "src/tint/utils/bitcast.h" #include "src/tint/utils/compiler_macros.h" #include "src/tint/utils/slice.h" -#include "src/tint/utils/string.h" -#include "src/tint/utils/string_stream.h" namespace tint::utils { @@ -587,12 +585,9 @@ Vector(Ts...) -> Vector, sizeof...(Ts)>; /// Aside from this move pattern, a VectorRef provides an immutable reference to the Vector. template class VectorRef { - /// The slice type used by this vector reference - using Slice = utils::Slice; - /// @returns an empty slice. - static Slice& EmptySlice() { - static Slice empty; + static utils::Slice& EmptySlice() { + static utils::Slice empty; return empty; } @@ -608,7 +603,7 @@ class VectorRef { /// Constructor from a Slice /// @param slice the slice - VectorRef(Slice& slice) // NOLINT(runtime/explicit) + VectorRef(utils::Slice& slice) // NOLINT(runtime/explicit) : slice_(slice) {} /// Constructor from a Vector @@ -621,7 +616,7 @@ class VectorRef { /// @param vector the vector to create a reference of template VectorRef(const Vector& vector) // NOLINT(runtime/explicit) - : slice_(const_cast(vector.impl_.slice)) {} + : slice_(const_cast&>(vector.impl_.slice)) {} /// Constructor from a moved Vector /// @param vector the vector being moved @@ -689,6 +684,9 @@ class VectorRef { return {slice_.template Reinterpret()}; } + /// @returns the internal slice of the vector + utils::Slice Slice() { return slice_; } + /// @returns true if the vector is empty. bool IsEmpty() const { return slice_.len == 0; } @@ -724,7 +722,7 @@ class VectorRef { friend class VectorRef; /// The slice of the vector being referenced. - Slice& slice_; + utils::Slice& slice_; /// Whether the slice data is passed by r-value reference, and can be moved. bool can_move_ = false; }; @@ -753,44 +751,6 @@ Vector ToVector(const std::vector& vector) { return out; } -/// Prints the vector @p vec to @p o -/// @param o the stream to write to -/// @param vec the vector -/// @return the stream so calls can be chained -template -inline utils::StringStream& operator<<(utils::StringStream& o, const utils::Vector& vec) { - o << "["; - bool first = true; - for (auto& el : vec) { - if (!first) { - o << ", "; - } - first = false; - o << ToString(el); - } - o << "]"; - return o; -} - -/// Prints the vector @p vec to @p o -/// @param o the stream to write to -/// @param vec the vector reference -/// @return the stream so calls can be chained -template -inline utils::StringStream& operator<<(utils::StringStream& o, utils::VectorRef vec) { - o << "["; - bool first = true; - for (auto& el : vec) { - if (!first) { - o << ", "; - } - first = false; - o << ToString(el); - } - o << "]"; - return o; -} - } // namespace tint::utils #endif // SRC_TINT_UTILS_VECTOR_H_