[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>
This commit is contained in:
Ben Clayton 2023-05-26 21:20:36 +00:00 committed by Dawn LUCI CQ
parent 0b82a3ba15
commit ca0b9ef49e
10 changed files with 224 additions and 78 deletions

View File

@ -22,6 +22,7 @@
#include "src/tint/ast/transform/test_helper.h" #include "src/tint/ast/transform/test_helper.h"
#include "src/tint/builtin/builtin.h" #include "src/tint/builtin/builtin.h"
#include "src/tint/builtin/texel_format.h" #include "src/tint/builtin/texel_format.h"
#include "src/tint/utils/string.h"
namespace tint::ast::transform { namespace tint::ast::transform {
namespace { namespace {

View File

@ -19,6 +19,7 @@
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "src/tint/utils/string.h"
#include "src/tint/utils/string_stream.h" #include "src/tint/utils/string_stream.h"
namespace tint::bench { namespace tint::bench {

View File

@ -66,6 +66,7 @@
#include "src/tint/utils/defer.h" #include "src/tint/utils/defer.h"
#include "src/tint/utils/map.h" #include "src/tint/utils/map.h"
#include "src/tint/utils/scoped_assignment.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/string_stream.h"
#include "src/tint/utils/unique_vector.h" #include "src/tint/utils/unique_vector.h"

View File

@ -3761,8 +3761,9 @@ bool Resolver::DiagnosticControl(const ast::DiagnosticControl& control) {
} else { } else {
utils::StringStream ss; utils::StringStream ss;
ss << "unrecognized diagnostic rule 'chromium." << name << "'\n"; ss << "unrecognized diagnostic rule 'chromium." << name << "'\n";
utils::SuggestAlternatives(name, builtin::kChromiumDiagnosticRuleStrings, ss, utils::SuggestAlternativeOptions opts;
"chromium."); opts.prefix = "chromium.";
utils::SuggestAlternatives(name, builtin::kChromiumDiagnosticRuleStrings, ss, opts);
AddWarning(ss.str(), control.rule_name->source); AddWarning(ss.str(), control.rule_name->source);
} }
} }

View File

@ -15,6 +15,7 @@
#include <algorithm> #include <algorithm>
#include "src/tint/utils/string.h" #include "src/tint/utils/string.h"
#include "src/tint/utils/transform.h"
#include "src/tint/utils/vector.h" #include "src/tint/utils/vector.h"
namespace tint::utils { 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, void SuggestAlternatives(std::string_view got,
Slice<char const* const> strings, Slice<char const* const> strings,
utils::StringStream& ss, 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, // 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. // suggest that. Don't bother with suggestions if the string was extremely long.
constexpr size_t kSuggestionDistance = 5; constexpr size_t kSuggestionDistance = 5;
constexpr size_t kSuggestionMaxLength = 64; constexpr size_t kSuggestionMaxLength = 64;
if (!got.empty() && got.size() < kSuggestionMaxLength) { if (!got.empty() && got.size() < kSuggestionMaxLength) {
size_t candidate_dist = kSuggestionDistance; size_t candidate_dist = kSuggestionDistance;
const char* candidate = nullptr; std::string_view candidate;
for (auto* str : strings) { for (auto str : strings) {
auto dist = utils::Distance(str, got); auto dist = utils::Distance(str, got);
if (dist < candidate_dist) { if (dist < candidate_dist) {
candidate = str; candidate = str;
candidate_dist = dist; candidate_dist = dist;
} }
} }
if (candidate) { if (!candidate.empty()) {
ss << "Did you mean '" << prefix << candidate << "'?\n"; ss << "Did you mean '" << options.prefix << candidate << "'?";
if (options.list_possible_values) {
ss << "\n";
}
} }
} }
// List all the possible enumerator values if (options.list_possible_values) {
ss << "Possible values: "; // List all the possible enumerator values
for (auto* str : strings) { ss << "Possible values: ";
if (str != strings[0]) { for (auto str : strings) {
ss << ", "; if (str != strings[0]) {
ss << ", ";
}
ss << "'" << options.prefix << str << "'";
} }
ss << "'" << prefix << str << "'";
} }
} }

View File

@ -20,6 +20,7 @@
#include "src/tint/utils/slice.h" #include "src/tint/utils/slice.h"
#include "src/tint/utils/string_stream.h" #include "src/tint/utils/string_stream.h"
#include "src/tint/utils/vector.h"
namespace tint::utils { namespace tint::utils {
@ -38,6 +39,12 @@ namespace tint::utils {
return str; 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 /// @param value the value to be printed as a string
/// @returns value printed as a string via the stream `<<` operator /// @returns value printed as a string via the stream `<<` operator
template <typename T> template <typename T>
@ -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 /// @returns the Levenshtein distance between @p a and @p b
size_t Distance(std::string_view a, std::string_view 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. /// Suggest alternatives for an unrecognized string from a list of possible values.
/// @param got the unrecognized string /// @param got the unrecognized string
/// @param strings the list of possible values /// @param strings the list of possible values
/// @param ss the stream to write the suggest and list of possible values to /// @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, void SuggestAlternatives(std::string_view got,
Slice<char const* const> strings, Slice<char const* const> strings,
utils::StringStream& ss, 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 str the input string
/// @param pred the predicate function /// @param pred the predicate function
@ -150,6 +175,51 @@ inline std::string_view TrimSpace(std::string_view str) {
return Trim(str, IsSpace); 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 } // namespace tint::utils
#endif // SRC_TINT_UTILS_STRING_H_ #endif // SRC_TINT_UTILS_STRING_H_

View File

@ -24,6 +24,7 @@
#include <utility> #include <utility>
#include "src/tint/utils/unicode.h" #include "src/tint/utils/unicode.h"
#include "src/tint/utils/vector.h"
namespace tint::utils { namespace tint::utils {
@ -189,6 +190,44 @@ class StringStream {
/// @returns out so calls can be chained /// @returns out so calls can be chained
utils::StringStream& operator<<(utils::StringStream& out, CodePoint codepoint); 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 } // namespace tint::utils
#endif // SRC_TINT_UTILS_STRING_STREAM_H_ #endif // SRC_TINT_UTILS_STRING_STREAM_H_

View File

@ -14,7 +14,7 @@
#include "src/tint/utils/string.h" #include "src/tint/utils/string.h"
#include "gtest/gtest.h" #include "gmock/gmock.h"
#include "src/tint/utils/string_stream.h" #include "src/tint/utils/string_stream.h"
namespace tint::utils { namespace tint::utils {
@ -34,6 +34,8 @@ TEST(StringTest, ReplaceAll) {
} }
TEST(StringTest, ToString) { TEST(StringTest, ToString) {
EXPECT_EQ("true", ToString(true));
EXPECT_EQ("false", ToString(false));
EXPECT_EQ("123", ToString(123)); EXPECT_EQ("123", ToString(123));
EXPECT_EQ("hello", ToString("hello")); EXPECT_EQ("hello", ToString("hello"));
} }
@ -82,6 +84,23 @@ Possible values: 'hello world', 'Hello World')");
SuggestAlternatives("hello world", alternatives, ss); SuggestAlternatives("hello world", alternatives, ss);
EXPECT_EQ(ss.str(), R"(Possible values: 'foobar', 'something else')"); 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) { TEST(StringTest, TrimLeft) {
@ -153,5 +172,27 @@ TEST(StringTest, TrimSpace) {
EXPECT_EQ(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<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
} // namespace tint::utils } // namespace tint::utils

View File

@ -85,6 +85,36 @@ auto Transform(const Vector<IN, N>& in, TRANSFORMER&& transform)
return result; 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. /// Transform performs an element-wise transformation of a vector reference.
/// @param in the input vector. /// @param in the input vector.
/// @param transform the transformation function with signature: `OUT(IN)` /// @param transform the transformation function with signature: `OUT(IN)`
@ -92,13 +122,7 @@ auto Transform(const Vector<IN, N>& in, TRANSFORMER&& transform)
/// @returns a new vector with each element of the source vector transformed by `transform`. /// @returns a new vector with each element of the source vector transformed by `transform`.
template <size_t N, typename IN, typename TRANSFORMER> template <size_t N, typename IN, typename TRANSFORMER>
auto Transform(VectorRef<IN> in, TRANSFORMER&& transform) -> Vector<decltype(transform(in[0])), N> { auto Transform(VectorRef<IN> in, TRANSFORMER&& transform) -> Vector<decltype(transform(in[0])), N> {
const auto count = in.Length(); return Transform<N>(in.Slice(), std::forward<TRANSFORMER>(transform));
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;
} }
/// Transform performs an element-wise transformation of a vector reference. /// Transform performs an element-wise transformation of a vector reference.
@ -109,13 +133,7 @@ auto Transform(VectorRef<IN> in, TRANSFORMER&& transform) -> Vector<decltype(tra
template <size_t N, typename IN, typename TRANSFORMER> template <size_t N, typename IN, typename TRANSFORMER>
auto Transform(VectorRef<IN> in, TRANSFORMER&& transform) auto Transform(VectorRef<IN> in, TRANSFORMER&& transform)
-> Vector<decltype(transform(in[0], 1u)), N> { -> Vector<decltype(transform(in[0], 1u)), N> {
const auto count = in.Length(); return Transform<N>(in.Slice(), std::forward<TRANSFORMER>(transform));
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;
} }
/// TransformN performs an element-wise transformation of a vector, transforming and returning at /// TransformN performs an element-wise transformation of a vector, transforming and returning at

View File

@ -26,8 +26,6 @@
#include "src/tint/utils/bitcast.h" #include "src/tint/utils/bitcast.h"
#include "src/tint/utils/compiler_macros.h" #include "src/tint/utils/compiler_macros.h"
#include "src/tint/utils/slice.h" #include "src/tint/utils/slice.h"
#include "src/tint/utils/string.h"
#include "src/tint/utils/string_stream.h"
namespace tint::utils { namespace tint::utils {
@ -587,12 +585,9 @@ Vector(Ts...) -> Vector<VectorCommonType<Ts...>, sizeof...(Ts)>;
/// Aside from this move pattern, a VectorRef provides an immutable reference to the Vector. /// Aside from this move pattern, a VectorRef provides an immutable reference to the Vector.
template <typename T> template <typename T>
class VectorRef { class VectorRef {
/// The slice type used by this vector reference
using Slice = utils::Slice<T>;
/// @returns an empty slice. /// @returns an empty slice.
static Slice& EmptySlice() { static utils::Slice<T>& EmptySlice() {
static Slice empty; static utils::Slice<T> empty;
return empty; return empty;
} }
@ -608,7 +603,7 @@ class VectorRef {
/// Constructor from a Slice /// Constructor from a Slice
/// @param slice the slice /// @param slice the slice
VectorRef(Slice& slice) // NOLINT(runtime/explicit) VectorRef(utils::Slice<T>& slice) // NOLINT(runtime/explicit)
: slice_(slice) {} : slice_(slice) {}
/// Constructor from a Vector /// Constructor from a Vector
@ -621,7 +616,7 @@ class VectorRef {
/// @param vector the vector to create a reference of /// @param vector the vector to create a reference of
template <size_t N> template <size_t N>
VectorRef(const Vector<T, N>& vector) // NOLINT(runtime/explicit) 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 /// Constructor from a moved Vector
/// @param vector the vector being moved /// @param vector the vector being moved
@ -689,6 +684,9 @@ class VectorRef {
return {slice_.template Reinterpret<U, ReinterpretMode::kUnsafe>()}; 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. /// @returns true if the vector is empty.
bool IsEmpty() const { return slice_.len == 0; } bool IsEmpty() const { return slice_.len == 0; }
@ -724,7 +722,7 @@ class VectorRef {
friend class VectorRef; friend class VectorRef;
/// The slice of the vector being referenced. /// 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. /// Whether the slice data is passed by r-value reference, and can be moved.
bool can_move_ = false; bool can_move_ = false;
}; };
@ -753,44 +751,6 @@ Vector<T, N> ToVector(const std::vector<T>& vector) {
return out; 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 } // namespace tint::utils
#endif // SRC_TINT_UTILS_VECTOR_H_ #endif // SRC_TINT_UTILS_VECTOR_H_