[tint][utils] Add more string helpers
Add utils::Split(), utils::Join() for splitting and joining strings with
delimiters.
Add options to SuggestAlternatives().
Add Quote() for surrounding a string with '.
Requires some include shuffling and minor tweaks to other utilities.
Change-Id: If75058e68cd986450fecc0b27bcc9a94c765a665
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/134580
Commit-Queue: Ben Clayton <bclayton@google.com>
Reviewed-by: James Price <jrprice@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Auto-Submit: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/ast/transform/renamer_test.cc b/src/tint/ast/transform/renamer_test.cc
index 4e97c6f..c93340d 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 4804a71..115d0b2 100644
--- a/src/tint/bench/benchmark.cc
+++ b/src/tint/bench/benchmark.cc
@@ -19,6 +19,7 @@
#include <utility>
#include <vector>
+#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 5ba9ee2..504e8ac 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 09d8464..fb06435 100644
--- a/src/tint/resolver/resolver.cc
+++ b/src/tint/resolver/resolver.cc
@@ -3761,8 +3761,9 @@
} 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 67eaf2b..00b51a8 100644
--- a/src/tint/utils/string.cc
+++ b/src/tint/utils/string.cc
@@ -15,6 +15,7 @@
#include <algorithm>
#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 @@
void SuggestAlternatives(std::string_view got,
Slice<char const* const> 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<std::string_view> 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 e731365..99c930a 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 @@
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 <typename T>
@@ -75,15 +82,33 @@
/// @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<char const* const> 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<std::string_view> strings,
+ utils::StringStream& ss,
+ const SuggestAlternativeOptions& options = {});
/// @param str the input string
/// @param pred the predicate function
@@ -150,6 +175,51 @@
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<std::string_view, 8> Split(std::string_view str, std::string_view delimiter) {
+ utils::Vector<std::string_view, 8> 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 <code>'</code>
+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 <typename T>
+inline std::string Join(utils::VectorRef<T> 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 <typename T, size_t N>
+inline std::string Join(const utils::Vector<T, N>& parts, std::string_view delimiter) {
+ return Join(utils::VectorRef<T>(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 7cbce09..ecb88f7 100644
--- a/src/tint/utils/string_stream.h
+++ b/src/tint/utils/string_stream.h
@@ -24,6 +24,7 @@
#include <utility>
#include "src/tint/utils/unicode.h"
+#include "src/tint/utils/vector.h"
namespace tint::utils {
@@ -189,6 +190,44 @@
/// @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 <typename T, size_t N>
+inline utils::StringStream& operator<<(utils::StringStream& o, const utils::Vector<T, N>& 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 <typename T>
+inline utils::StringStream& operator<<(utils::StringStream& o, utils::VectorRef<T> 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 9cf8370..676c341 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, 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 @@
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 @@
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<int, 1>{}, ","), "");
+ 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 9615471..7ecd941 100644
--- a/src/tint/utils/transform.h
+++ b/src/tint/utils/transform.h
@@ -85,6 +85,36 @@
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 <size_t N, typename IN, typename TRANSFORMER>
+auto Transform(Slice<IN> in, TRANSFORMER&& transform) -> Vector<decltype(transform(in[0])), N> {
+ Vector<decltype(transform(in[0])), N> 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 <size_t N, typename IN, typename TRANSFORMER>
+auto Transform(Slice<IN> in, TRANSFORMER&& transform) -> Vector<decltype(transform(in[0], 1u)), N> {
+ Vector<decltype(transform(in[0], 1u)), N> 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 @@
/// @returns a new vector with each element of the source vector transformed by `transform`.
template <size_t N, typename IN, typename TRANSFORMER>
auto Transform(VectorRef<IN> in, TRANSFORMER&& transform) -> Vector<decltype(transform(in[0])), N> {
- const auto count = in.Length();
- Vector<decltype(transform(in[0])), N> result;
- result.Reserve(count);
- for (size_t i = 0; i < count; ++i) {
- result.Push(transform(in[i]));
- }
- return result;
+ return Transform<N>(in.Slice(), std::forward<TRANSFORMER>(transform));
}
/// Transform performs an element-wise transformation of a vector reference.
@@ -109,13 +133,7 @@
template <size_t N, typename IN, typename TRANSFORMER>
auto Transform(VectorRef<IN> in, TRANSFORMER&& transform)
-> Vector<decltype(transform(in[0], 1u)), N> {
- const auto count = in.Length();
- Vector<decltype(transform(in[0], 1u)), N> result;
- result.Reserve(count);
- for (size_t i = 0; i < count; ++i) {
- result.Push(transform(in[i], i));
- }
- return result;
+ return Transform<N>(in.Slice(), std::forward<TRANSFORMER>(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 65595e7..acb0e39 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 @@
/// Aside from this move pattern, a VectorRef provides an immutable reference to the Vector.
template <typename T>
class VectorRef {
- /// The slice type used by this vector reference
- using Slice = utils::Slice<T>;
-
/// @returns an empty slice.
- static Slice& EmptySlice() {
- static Slice empty;
+ static utils::Slice<T>& EmptySlice() {
+ static utils::Slice<T> empty;
return empty;
}
@@ -608,7 +603,7 @@
/// Constructor from a Slice
/// @param slice the slice
- VectorRef(Slice& slice) // NOLINT(runtime/explicit)
+ VectorRef(utils::Slice<T>& slice) // NOLINT(runtime/explicit)
: slice_(slice) {}
/// Constructor from a Vector
@@ -621,7 +616,7 @@
/// @param vector the vector to create a reference of
template <size_t N>
VectorRef(const Vector<T, N>& vector) // NOLINT(runtime/explicit)
- : slice_(const_cast<Slice&>(vector.impl_.slice)) {}
+ : slice_(const_cast<utils::Slice<T>&>(vector.impl_.slice)) {}
/// Constructor from a moved Vector
/// @param vector the vector being moved
@@ -689,6 +684,9 @@
return {slice_.template Reinterpret<U, ReinterpretMode::kUnsafe>()};
}
+ /// @returns the internal slice of the vector
+ utils::Slice<T> Slice() { return slice_; }
+
/// @returns true if the vector is empty.
bool IsEmpty() const { return slice_.len == 0; }
@@ -724,7 +722,7 @@
friend class VectorRef;
/// The slice of the vector being referenced.
- Slice& slice_;
+ utils::Slice<T>& slice_;
/// Whether the slice data is passed by r-value reference, and can be moved.
bool can_move_ = false;
};
@@ -753,44 +751,6 @@
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 <typename T, size_t N>
-inline utils::StringStream& operator<<(utils::StringStream& o, const utils::Vector<T, N>& 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 <typename T>
-inline utils::StringStream& operator<<(utils::StringStream& o, utils::VectorRef<T> 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_