From 6a262fdaddb6cd7df7ddc8472a1cc61cc64a01db Mon Sep 17 00:00:00 2001 From: Abseil Team Date: Wed, 31 Aug 2022 12:29:19 -0700 Subject: Adds support for "%v" in absl::StrFormat and related functions for string-like types (support for other builtin types will follow in future changes). Rather than specifying %s for strings, users may specify %v and have the format specifier deduced. Notably, %v does not work for `const char*` because we cannot be certain if %s or %p was intended (nor can we be certain if the `const char*` was properly null-terminated). If you have a `const char*` you know is null-terminated and would like to work with %v, please wrap it in a `string_view` before using it. PiperOrigin-RevId: 471321055 Change-Id: Ifbb50082d301baecc7edc277975f12e7ad3ecc8a --- absl/strings/str_format_test.cc | 207 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 207 insertions(+) (limited to 'absl/strings/str_format_test.cc') diff --git a/absl/strings/str_format_test.cc b/absl/strings/str_format_test.cc index 804e6c22..7bffbb7a 100644 --- a/absl/strings/str_format_test.cc +++ b/absl/strings/str_format_test.cc @@ -84,6 +84,11 @@ TEST_F(FormatEntryPointTest, StringFormat) { EXPECT_EQ("=123=", StrFormat(view, 123)); } +TEST_F(FormatEntryPointTest, StringFormatV) { + std::string hello = "hello"; + EXPECT_EQ("hello", StrFormat("%v", hello)); +} + TEST_F(FormatEntryPointTest, AppendFormat) { std::string s; std::string& r = StrAppendFormat(&s, "%d", 123); @@ -165,6 +170,21 @@ TEST_F(FormatEntryPointTest, FormatCountCaptureExample) { s); } +TEST_F(FormatEntryPointTest, FormatCountCaptureExampleWithV) { + int n; + std::string s; + std::string a1 = "(1,1)"; + std::string a2 = "(1,2)"; + std::string a3 = "(2,2)"; + StrAppendFormat(&s, "%v: %n%v\n", a1, FormatCountCapture(&n), a2); + StrAppendFormat(&s, "%*s%v\n", n, "", a3); + EXPECT_EQ(7, n); + EXPECT_EQ( + "(1,1): (1,2)\n" + " (2,2)\n", + s); +} + TEST_F(FormatEntryPointTest, Stream) { const std::string formats[] = { "", @@ -191,6 +211,26 @@ TEST_F(FormatEntryPointTest, Stream) { } } +TEST_F(FormatEntryPointTest, StreamWithV) { + const std::string format = "%d %u %c %v %f %g"; + + const std::string format_for_buf = "%d %u %c %s %f %g"; + + std::string buf(4096, '\0'); + const auto parsed = + ParsedFormat<'d', 'u', 'c', 'v', 'f', 'g'>::NewAllowIgnored(format); + std::ostringstream oss; + oss << StreamFormat(*parsed, 123, 3, 49, + absl::string_view("multistreaming!!!"), 1.01, 1.01); + int fmt_result = + snprintf(&*buf.begin(), buf.size(), format_for_buf.c_str(), // + 123, 3, 49, "multistreaming!!!", 1.01, 1.01); + ASSERT_TRUE(oss) << format; + ASSERT_TRUE(fmt_result >= 0 && static_cast(fmt_result) < buf.size()) + << fmt_result; + EXPECT_EQ(buf.c_str(), oss.str()); +} + TEST_F(FormatEntryPointTest, StreamOk) { std::ostringstream oss; oss << StreamFormat("hello %d", 123); @@ -249,6 +289,14 @@ TEST_F(FormatEntryPointTest, FormatStreamed) { EXPECT_EQ("123", StrFormat("%s", FormatStreamed(StreamFormat("%d", 123)))); } +TEST_F(FormatEntryPointTest, FormatStreamedWithV) { + EXPECT_EQ("123", StrFormat("%v", FormatStreamed(123))); + EXPECT_EQ(" 123", StrFormat("%5v", FormatStreamed(123))); + EXPECT_EQ("123 ", StrFormat("%-5v", FormatStreamed(123))); + EXPECT_EQ("X", StrFormat("%v", FormatStreamed(streamed_test::X()))); + EXPECT_EQ("123", StrFormat("%v", FormatStreamed(StreamFormat("%d", 123)))); +} + // Helper class that creates a temporary file and exposes a FILE* to it. // It will close the file on destruction. class TempFile { @@ -284,6 +332,14 @@ TEST_F(FormatEntryPointTest, FPrintF) { EXPECT_EQ(tmp.ReadFile(), "STRING: ABC NUMBER: -000000019"); } +TEST_F(FormatEntryPointTest, FPrintFWithV) { + TempFile tmp; + int result = + FPrintF(tmp.file(), "STRING: %v NUMBER: %010d", std::string("ABC"), -19); + EXPECT_EQ(result, 30); + EXPECT_EQ(tmp.ReadFile(), "STRING: ABC NUMBER: -000000019"); +} + TEST_F(FormatEntryPointTest, FPrintFError) { errno = 0; int result = FPrintF(stdin, "ABC"); @@ -318,6 +374,23 @@ TEST_F(FormatEntryPointTest, PrintF) { EXPECT_EQ(result, 30); EXPECT_EQ(tmp.ReadFile(), "STRING: ABC NUMBER: -000000019"); } + +TEST_F(FormatEntryPointTest, PrintFWithV) { + int stdout_tmp = dup(STDOUT_FILENO); + + TempFile tmp; + std::fflush(stdout); + dup2(fileno(tmp.file()), STDOUT_FILENO); + + int result = PrintF("STRING: %v NUMBER: %010d", std::string("ABC"), -19); + + std::fflush(stdout); + dup2(stdout_tmp, STDOUT_FILENO); + close(stdout_tmp); + + EXPECT_EQ(result, 30); + EXPECT_EQ(tmp.ReadFile(), "STRING: ABC NUMBER: -000000019"); +} #endif // __GLIBC__ TEST_F(FormatEntryPointTest, SNPrintF) { @@ -347,9 +420,41 @@ TEST_F(FormatEntryPointTest, SNPrintF) { EXPECT_EQ(result, 37); } +TEST_F(FormatEntryPointTest, SNPrintFWithV) { + char buffer[16]; + int result = + SNPrintF(buffer, sizeof(buffer), "STRING: %v", std::string("ABC")); + EXPECT_EQ(result, 11); + EXPECT_EQ(std::string(buffer), "STRING: ABC"); + + result = SNPrintF(buffer, sizeof(buffer), "NUMBER: %d", 123456); + EXPECT_EQ(result, 14); + EXPECT_EQ(std::string(buffer), "NUMBER: 123456"); + + result = SNPrintF(buffer, sizeof(buffer), "NUMBER: %d", 1234567); + EXPECT_EQ(result, 15); + EXPECT_EQ(std::string(buffer), "NUMBER: 1234567"); + + result = SNPrintF(buffer, sizeof(buffer), "NUMBER: %d", 12345678); + EXPECT_EQ(result, 16); + EXPECT_EQ(std::string(buffer), "NUMBER: 1234567"); + + result = SNPrintF(buffer, sizeof(buffer), "NUMBER: %d", 123456789); + EXPECT_EQ(result, 17); + EXPECT_EQ(std::string(buffer), "NUMBER: 1234567"); + + std::string size = "size"; + + result = SNPrintF(nullptr, 0, "Just checking the %v of the output.", size); + EXPECT_EQ(result, 37); +} + TEST(StrFormat, BehavesAsDocumented) { std::string s = absl::StrFormat("%s, %d!", "Hello", 123); EXPECT_EQ("Hello, 123!", s); + std::string hello = "Hello"; + std::string s2 = absl::StrFormat("%v, %d!", hello, 123); + EXPECT_EQ("Hello, 123!", s2); // The format of a replacement is // '%'[position][flags][width['.'precision]][length_modifier][format] EXPECT_EQ(absl::StrFormat("%1$+3.2Lf", 1.1), "+1.10"); @@ -364,9 +469,13 @@ TEST(StrFormat, BehavesAsDocumented) { // "s" - string Eg: "C" -> "C", std::string("C++") -> "C++" // Formats std::string, char*, string_view, and Cord. EXPECT_EQ(StrFormat("%s", "C"), "C"); + EXPECT_EQ(StrFormat("%v", std::string("C")), "C"); EXPECT_EQ(StrFormat("%s", std::string("C++")), "C++"); + EXPECT_EQ(StrFormat("%v", std::string("C++")), "C++"); EXPECT_EQ(StrFormat("%s", string_view("view")), "view"); + EXPECT_EQ(StrFormat("%v", string_view("view")), "view"); EXPECT_EQ(StrFormat("%s", absl::Cord("cord")), "cord"); + EXPECT_EQ(StrFormat("%v", absl::Cord("cord")), "cord"); // Integral Conversion // These format integral types: char, int, long, uint64_t, etc. EXPECT_EQ(StrFormat("%d", char{10}), "10"); @@ -490,6 +599,15 @@ TEST_F(ParsedFormatTest, SimpleChecked) { SummarizeParsedFormat(ParsedFormat<'s', '*', 'd'>("%s %.*d"))); } +TEST_F(ParsedFormatTest, SimpleCheckedWithV) { + EXPECT_EQ("[ABC]{d:1$d}[DEF]", + SummarizeParsedFormat(ParsedFormat<'d'>("ABC%dDEF"))); + EXPECT_EQ("{v:1$v}[FFF]{d:2$d}[ZZZ]{f:3$f}", + SummarizeParsedFormat(ParsedFormat<'v', 'd', 'f'>("%vFFF%dZZZ%f"))); + EXPECT_EQ("{v:1$v}[ ]{.*d:3$.2$*d}", + SummarizeParsedFormat(ParsedFormat<'v', '*', 'd'>("%v %.*d"))); +} + TEST_F(ParsedFormatTest, SimpleUncheckedCorrect) { auto f = ParsedFormat<'d'>::New("ABC%dDEF"); ASSERT_TRUE(f); @@ -520,6 +638,23 @@ TEST_F(ParsedFormatTest, SimpleUncheckedCorrect) { SummarizeParsedFormat(*dollar)); } +TEST_F(ParsedFormatTest, SimpleUncheckedCorrectWithV) { + auto f = ParsedFormat<'d'>::New("ABC%dDEF"); + ASSERT_TRUE(f); + EXPECT_EQ("[ABC]{d:1$d}[DEF]", SummarizeParsedFormat(*f)); + + std::string format = "%vFFF%dZZZ%f"; + auto f2 = ParsedFormat<'v', 'd', 'f'>::New(format); + + ASSERT_TRUE(f2); + EXPECT_EQ("{v:1$v}[FFF]{d:2$d}[ZZZ]{f:3$f}", SummarizeParsedFormat(*f2)); + + f2 = ParsedFormat<'v', 'd', 'f'>::New("%v %d %f"); + + ASSERT_TRUE(f2); + EXPECT_EQ("{v:1$v}[ ]{d:2$d}[ ]{f:3$f}", SummarizeParsedFormat(*f2)); +} + TEST_F(ParsedFormatTest, SimpleUncheckedIgnoredArgs) { EXPECT_FALSE((ParsedFormat<'d', 's'>::New("ABC"))); EXPECT_FALSE((ParsedFormat<'d', 's'>::New("%dABC"))); @@ -549,6 +684,15 @@ TEST_F(ParsedFormatTest, SimpleUncheckedIncorrect) { EXPECT_FALSE((ParsedFormat<'s', 'd', 'g'>::New(format))); } +TEST_F(ParsedFormatTest, SimpleUncheckedIncorrectWithV) { + EXPECT_FALSE(ParsedFormat<'d'>::New("")); + + EXPECT_FALSE(ParsedFormat<'d'>::New("ABC%dDEF%d")); + + std::string format = "%vFFF%dZZZ%f"; + EXPECT_FALSE((ParsedFormat<'v', 'd', 'g'>::New(format))); +} + #if defined(__cpp_nontype_template_parameter_auto) template @@ -595,6 +739,23 @@ TEST_F(ParsedFormatTest, ExtendedTyping) { 's'>::New("%s%s"); ASSERT_TRUE(v4); } + +TEST_F(ParsedFormatTest, ExtendedTypingWithV) { + EXPECT_FALSE(ParsedFormat::New("")); + ASSERT_TRUE(ParsedFormat::New("%d")); + auto v1 = ParsedFormat<'d', absl::FormatConversionCharSet::v>::New("%d%v"); + ASSERT_TRUE(v1); + auto v2 = ParsedFormat::New("%d%v"); + ASSERT_TRUE(v2); + auto v3 = ParsedFormat::New("%d%v"); + ASSERT_TRUE(v3); + auto v4 = ParsedFormat::New("%v%v"); + ASSERT_TRUE(v4); +} #endif TEST_F(ParsedFormatTest, UncheckedCorrect) { @@ -638,6 +799,28 @@ TEST_F(ParsedFormatTest, UncheckedCorrect) { SummarizeParsedFormat(*dollar)); } +TEST_F(ParsedFormatTest, UncheckedCorrectWithV) { + auto f = + ExtendedParsedFormat::New("ABC%dDEF"); + ASSERT_TRUE(f); + EXPECT_EQ("[ABC]{d:1$d}[DEF]", SummarizeParsedFormat(*f)); + + std::string format = "%vFFF%dZZZ%f"; + auto f2 = ExtendedParsedFormat< + absl::FormatConversionCharSet::v, absl::FormatConversionCharSet::d, + absl::FormatConversionCharSet::kFloating>::New(format); + + ASSERT_TRUE(f2); + EXPECT_EQ("{v:1$v}[FFF]{d:2$d}[ZZZ]{f:3$f}", SummarizeParsedFormat(*f2)); + + f2 = ExtendedParsedFormat< + absl::FormatConversionCharSet::v, absl::FormatConversionCharSet::d, + absl::FormatConversionCharSet::kFloating>::New("%v %d %f"); + + ASSERT_TRUE(f2); + EXPECT_EQ("{v:1$v}[ ]{d:2$d}[ ]{f:3$f}", SummarizeParsedFormat(*f2)); +} + TEST_F(ParsedFormatTest, UncheckedIgnoredArgs) { EXPECT_FALSE( (ExtendedParsedFormat::New(format))); } +TEST_F(ParsedFormatTest, UncheckedIncorrectWithV) { + EXPECT_FALSE(ExtendedParsedFormat::New("")); + + EXPECT_FALSE(ExtendedParsedFormat::New( + "ABC%dDEF%d")); + + std::string format = "%vFFF%dZZZ%f"; + EXPECT_FALSE( + (ExtendedParsedFormat::New(format))); +} + TEST_F(ParsedFormatTest, RegressionMixPositional) { EXPECT_FALSE( (ExtendedParsedFormat format("%s there"); EXPECT_EQ(WrappedFormat(format, "hello"), "hello there"); } +TEST_F(FormatWrapperTest, ParsedFormatWithV) { + std::string hello = "hello"; + ParsedFormat<'v'> format("%v there"); + EXPECT_EQ(WrappedFormat(format, hello), "hello there"); +} + } // namespace ABSL_NAMESPACE_END } // namespace absl -- cgit v1.2.3 From d3c00b06b334311e23ab18f5d2b90938a07ce35d Mon Sep 17 00:00:00 2001 From: Abseil Team Date: Tue, 6 Sep 2022 12:58:40 -0700 Subject: Fixes bug so that `%v` with modifiers doesn't compile. `%v` is not intended to work with modifiers because the meaning of modifiers is type-dependent and `%v` is intended to be used in situations where the type is not important. Please continue using if `%s` if you require format modifiers. PiperOrigin-RevId: 472534916 Change-Id: I5838761b2b40cbc4344077f23d44b1e634e5bae3 --- absl/strings/internal/str_format/checker.h | 4 ++++ absl/strings/str_format_test.cc | 2 -- 2 files changed, 4 insertions(+), 2 deletions(-) (limited to 'absl/strings/str_format_test.cc') diff --git a/absl/strings/internal/str_format/checker.h b/absl/strings/internal/str_format/checker.h index 2542f31d..aeb9d48d 100644 --- a/absl/strings/internal/str_format/checker.h +++ b/absl/strings/internal/str_format/checker.h @@ -212,6 +212,10 @@ class ConvParser { constexpr ConvParser ParseConversion() const { char first_char = GetChar(format_, 0); + if (first_char == 'v' && *(format_.data() - 1) != '%') { + return SetError(true); + } + if (is_positional_) { return VerifyPositional({ConsumeFront(format_), arg_position_}, first_char); diff --git a/absl/strings/str_format_test.cc b/absl/strings/str_format_test.cc index 7bffbb7a..4ac6aa27 100644 --- a/absl/strings/str_format_test.cc +++ b/absl/strings/str_format_test.cc @@ -291,8 +291,6 @@ TEST_F(FormatEntryPointTest, FormatStreamed) { TEST_F(FormatEntryPointTest, FormatStreamedWithV) { EXPECT_EQ("123", StrFormat("%v", FormatStreamed(123))); - EXPECT_EQ(" 123", StrFormat("%5v", FormatStreamed(123))); - EXPECT_EQ("123 ", StrFormat("%-5v", FormatStreamed(123))); EXPECT_EQ("X", StrFormat("%v", FormatStreamed(streamed_test::X()))); EXPECT_EQ("123", StrFormat("%v", FormatStreamed(StreamFormat("%d", 123)))); } -- cgit v1.2.3 From ab2e2c4f6062999afaf960759dfccb77f350c702 Mon Sep 17 00:00:00 2001 From: Abseil Team Date: Thu, 15 Sep 2022 14:29:39 -0700 Subject: Adds support for "%v" in absl::StrFormat and related functions for numeric types, including integer and floating point values. Users may now specify %v and have the format specifier deduced. Integer values will print according to %d specifications, unsigned values will use %u, and floating point values will use %g. Note that %v does not work for `char` due to ambiguity regarding the intended output. Please continue to use %c for `char`. PiperOrigin-RevId: 474658166 Change-Id: Iecae39263e368b27232db440535f2bf7da8d863c --- absl/strings/internal/str_format/arg.cc | 49 +++-- absl/strings/internal/str_format/arg.h | 26 ++- absl/strings/internal/str_format/checker_test.cc | 4 +- absl/strings/internal/str_format/extension.h | 2 + absl/strings/internal/str_format/extension_test.cc | 11 + absl/strings/str_format_test.cc | 237 ++++++++++++++++----- 6 files changed, 250 insertions(+), 79 deletions(-) (limited to 'absl/strings/str_format_test.cc') diff --git a/absl/strings/internal/str_format/arg.cc b/absl/strings/internal/str_format/arg.cc index 007e8e83..b0e06bfb 100644 --- a/absl/strings/internal/str_format/arg.cc +++ b/absl/strings/internal/str_format/arg.cc @@ -278,12 +278,33 @@ bool ConvertIntImplInnerSlow(const IntDigits &as_digits, return true; } +template ::value && + std::is_signed::value) || + std::is_same::value, + int>::type = 0> +constexpr auto ConvertV(T) { + return FormatConversionCharInternal::d; +} + +template ::value && + std::is_unsigned::value) || + std::is_same::value, + int>::type = 0> +constexpr auto ConvertV(T) { + return FormatConversionCharInternal::u; +} + template -bool ConvertIntArg(T v, const FormatConversionSpecImpl conv, - FormatSinkImpl *sink) { +bool ConvertIntArg(T v, FormatConversionSpecImpl conv, FormatSinkImpl *sink) { using U = typename MakeUnsigned::type; IntDigits as_digits; + if (conv.conversion_char() == FormatConversionCharInternal::v) { + conv.set_conversion_char(ConvertV(T{})); + } + // This odd casting is due to a bug in -Wswitch behavior in gcc49 which causes // it to complain about a switch/case type mismatch, even though both are // FormatConverionChar. Likely this is because at this point @@ -334,8 +355,11 @@ bool ConvertIntArg(T v, const FormatConversionSpecImpl conv, } template -bool ConvertFloatArg(T v, const FormatConversionSpecImpl conv, - FormatSinkImpl *sink) { +bool ConvertFloatArg(T v, FormatConversionSpecImpl conv, FormatSinkImpl *sink) { + if (conv.conversion_char() == FormatConversionCharInternal::v) { + conv.set_conversion_char(FormatConversionCharInternal::g); + } + return FormatConversionCharIsFloat(conv.conversion_char()) && ConvertFloatImpl(v, conv, sink); } @@ -413,19 +437,18 @@ FloatingConvertResult FormatConvertImpl(long double v, } // ==================== Chars ==================== -IntegralConvertResult FormatConvertImpl(char v, - const FormatConversionSpecImpl conv, - FormatSinkImpl *sink) { +CharConvertResult FormatConvertImpl(char v, const FormatConversionSpecImpl conv, + FormatSinkImpl *sink) { return {ConvertIntArg(v, conv, sink)}; } -IntegralConvertResult FormatConvertImpl(signed char v, - const FormatConversionSpecImpl conv, - FormatSinkImpl *sink) { +CharConvertResult FormatConvertImpl(signed char v, + const FormatConversionSpecImpl conv, + FormatSinkImpl *sink) { return {ConvertIntArg(v, conv, sink)}; } -IntegralConvertResult FormatConvertImpl(unsigned char v, - const FormatConversionSpecImpl conv, - FormatSinkImpl *sink) { +CharConvertResult FormatConvertImpl(unsigned char v, + const FormatConversionSpecImpl conv, + FormatSinkImpl *sink) { return {ConvertIntArg(v, conv, sink)}; } diff --git a/absl/strings/internal/str_format/arg.h b/absl/strings/internal/str_format/arg.h index a56ca301..3c2bb3e6 100644 --- a/absl/strings/internal/str_format/arg.h +++ b/absl/strings/internal/str_format/arg.h @@ -174,11 +174,17 @@ StringConvertResult FormatConvertImpl(const AbslCord& value, } using IntegralConvertResult = ArgConvertResult; +using FloatingConvertResult = ArgConvertResult; +using CharConvertResult = ArgConvertResult; -using FloatingConvertResult = - ArgConvertResult; // Floats. FloatingConvertResult FormatConvertImpl(float v, FormatConversionSpecImpl conv, @@ -190,14 +196,14 @@ FloatingConvertResult FormatConvertImpl(long double v, FormatSinkImpl* sink); // Chars. -IntegralConvertResult FormatConvertImpl(char v, FormatConversionSpecImpl conv, - FormatSinkImpl* sink); -IntegralConvertResult FormatConvertImpl(signed char v, - FormatConversionSpecImpl conv, - FormatSinkImpl* sink); -IntegralConvertResult FormatConvertImpl(unsigned char v, - FormatConversionSpecImpl conv, - FormatSinkImpl* sink); +CharConvertResult FormatConvertImpl(char v, FormatConversionSpecImpl conv, + FormatSinkImpl* sink); +CharConvertResult FormatConvertImpl(signed char v, + FormatConversionSpecImpl conv, + FormatSinkImpl* sink); +CharConvertResult FormatConvertImpl(unsigned char v, + FormatConversionSpecImpl conv, + FormatSinkImpl* sink); // Ints. IntegralConvertResult FormatConvertImpl(short v, // NOLINT diff --git a/absl/strings/internal/str_format/checker_test.cc b/absl/strings/internal/str_format/checker_test.cc index b186e7d8..680517f7 100644 --- a/absl/strings/internal/str_format/checker_test.cc +++ b/absl/strings/internal/str_format/checker_test.cc @@ -45,10 +45,10 @@ TEST(StrFormatChecker, ArgumentToConv) { EXPECT_EQ(ConvToString(conv), "sp"); conv = ArgumentToConv(); - EXPECT_EQ(ConvToString(conv), "fFeEgGaA"); + EXPECT_EQ(ConvToString(conv), "fFeEgGaAv"); conv = ArgumentToConv(); - EXPECT_EQ(ConvToString(conv), "cdiouxXfFeEgGaA*"); + EXPECT_EQ(ConvToString(conv), "cdiouxXfFeEgGaAv*"); conv = ArgumentToConv(); EXPECT_EQ(ConvToString(conv), "p"); diff --git a/absl/strings/internal/str_format/extension.h b/absl/strings/internal/str_format/extension.h index f8d98bf7..603bd49d 100644 --- a/absl/strings/internal/str_format/extension.h +++ b/absl/strings/internal/str_format/extension.h @@ -292,6 +292,8 @@ class FormatConversionSpecImpl { return conv_; } + void set_conversion_char(FormatConversionChar c) { conv_ = c; } + // Returns the specified width. If width is unspecfied, it returns a negative // value. int width() const { return width_; } diff --git a/absl/strings/internal/str_format/extension_test.cc b/absl/strings/internal/str_format/extension_test.cc index 1c93fdb1..694c1264 100644 --- a/absl/strings/internal/str_format/extension_test.cc +++ b/absl/strings/internal/str_format/extension_test.cc @@ -19,6 +19,7 @@ #include #include +#include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/strings/str_format.h" #include "absl/strings/string_view.h" @@ -95,4 +96,14 @@ TEST(FormatExtensionTest, VerifyEnumEquality) { #undef X_VAL } +TEST(FormatExtensionTest, SetConversionChar) { + absl::str_format_internal::FormatConversionSpecImpl spec; + EXPECT_EQ(spec.conversion_char(), + absl::str_format_internal::FormatConversionCharInternal::kNone); + spec.set_conversion_char( + absl::str_format_internal::FormatConversionCharInternal::d); + EXPECT_EQ(spec.conversion_char(), + absl::str_format_internal::FormatConversionCharInternal::d); +} + } // namespace diff --git a/absl/strings/str_format_test.cc b/absl/strings/str_format_test.cc index 4ac6aa27..9ef69837 100644 --- a/absl/strings/str_format_test.cc +++ b/absl/strings/str_format_test.cc @@ -42,6 +42,18 @@ TEST_F(FormatEntryPointTest, Format) { EXPECT_TRUE(Format(&sink, pc, 123)); EXPECT_EQ("A format 123", sink); } + +TEST_F(FormatEntryPointTest, FormatWithV) { + std::string sink; + EXPECT_TRUE(Format(&sink, "A format %v", 123)); + EXPECT_EQ("A format 123", sink); + sink.clear(); + + ParsedFormat<'v'> pc("A format %v"); + EXPECT_TRUE(Format(&sink, pc, 123)); + EXPECT_EQ("A format 123", sink); +} + TEST_F(FormatEntryPointTest, UntypedFormat) { constexpr const char* formats[] = { "", @@ -87,6 +99,9 @@ TEST_F(FormatEntryPointTest, StringFormat) { TEST_F(FormatEntryPointTest, StringFormatV) { std::string hello = "hello"; EXPECT_EQ("hello", StrFormat("%v", hello)); + EXPECT_EQ("123", StrFormat("%v", 123)); + constexpr absl::string_view view("=%v=", 4); + EXPECT_EQ("=123=", StrFormat(view, 123)); } TEST_F(FormatEntryPointTest, AppendFormat) { @@ -96,6 +111,13 @@ TEST_F(FormatEntryPointTest, AppendFormat) { EXPECT_EQ("123", r); } +TEST_F(FormatEntryPointTest, AppendFormatWithV) { + std::string s; + std::string& r = StrAppendFormat(&s, "%v", 123); + EXPECT_EQ(&s, &r); // should be same object + EXPECT_EQ("123", r); +} + TEST_F(FormatEntryPointTest, AppendFormatFail) { std::string s = "orig"; @@ -108,6 +130,17 @@ TEST_F(FormatEntryPointTest, AppendFormatFail) { {&arg, 1})); } +TEST_F(FormatEntryPointTest, AppendFormatFailWithV) { + std::string s = "orig"; + + UntypedFormatSpec format(" more %v"); + FormatArgImpl arg("not an int"); + + EXPECT_EQ("orig", + str_format_internal::AppendPack( + &s, str_format_internal::UntypedFormatSpecImpl::Extract(format), + {&arg, 1})); +} TEST_F(FormatEntryPointTest, ManyArgs) { EXPECT_EQ("24", StrFormat("%24$d", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, @@ -128,6 +161,15 @@ TEST_F(FormatEntryPointTest, Preparsed) { EXPECT_EQ("=123=", StrFormat(ParsedFormat<'d'>(view), 123)); } +TEST_F(FormatEntryPointTest, PreparsedWithV) { + ParsedFormat<'v'> pc("%v"); + EXPECT_EQ("123", StrFormat(pc, 123)); + // rvalue ok? + EXPECT_EQ("123", StrFormat(ParsedFormat<'v'>("%v"), 123)); + constexpr absl::string_view view("=%v=", 4); + EXPECT_EQ("=123=", StrFormat(ParsedFormat<'v'>(view), 123)); +} + TEST_F(FormatEntryPointTest, FormatCountCapture) { int n = 0; EXPECT_EQ("", StrFormat("%n", FormatCountCapture(&n))); @@ -136,6 +178,14 @@ TEST_F(FormatEntryPointTest, FormatCountCapture) { EXPECT_EQ(3, n); } +TEST_F(FormatEntryPointTest, FormatCountCaptureWithV) { + int n = 0; + EXPECT_EQ("", StrFormat("%n", FormatCountCapture(&n))); + EXPECT_EQ(0, n); + EXPECT_EQ("123", StrFormat("%v%n", 123, FormatCountCapture(&n))); + EXPECT_EQ(3, n); +} + TEST_F(FormatEntryPointTest, FormatCountCaptureWrongType) { // Should reject int*. int n = 0; @@ -148,6 +198,18 @@ TEST_F(FormatEntryPointTest, FormatCountCaptureWrongType) { absl::MakeSpan(args))); } +TEST_F(FormatEntryPointTest, FormatCountCaptureWrongTypeWithV) { + // Should reject int*. + int n = 0; + UntypedFormatSpec format("%v%n"); + int i = 123, *ip = &n; + FormatArgImpl args[2] = {FormatArgImpl(i), FormatArgImpl(ip)}; + + EXPECT_EQ("", str_format_internal::FormatPack( + str_format_internal::UntypedFormatSpecImpl::Extract(format), + absl::MakeSpan(args))); +} + TEST_F(FormatEntryPointTest, FormatCountCaptureMultiple) { int n1 = 0; int n2 = 0; @@ -212,23 +274,33 @@ TEST_F(FormatEntryPointTest, Stream) { } TEST_F(FormatEntryPointTest, StreamWithV) { - const std::string format = "%d %u %c %v %f %g"; + const std::string formats[] = { + "", + "a", + "%v %u %c %v %f %v", + }; - const std::string format_for_buf = "%d %u %c %s %f %g"; + const std::string formats_for_buf[] = { + "", + "a", + "%d %u %c %s %f %g", + }; std::string buf(4096, '\0'); - const auto parsed = - ParsedFormat<'d', 'u', 'c', 'v', 'f', 'g'>::NewAllowIgnored(format); - std::ostringstream oss; - oss << StreamFormat(*parsed, 123, 3, 49, - absl::string_view("multistreaming!!!"), 1.01, 1.01); - int fmt_result = - snprintf(&*buf.begin(), buf.size(), format_for_buf.c_str(), // - 123, 3, 49, "multistreaming!!!", 1.01, 1.01); - ASSERT_TRUE(oss) << format; - ASSERT_TRUE(fmt_result >= 0 && static_cast(fmt_result) < buf.size()) - << fmt_result; - EXPECT_EQ(buf.c_str(), oss.str()); + for (auto i = 0; i < ABSL_ARRAYSIZE(formats); ++i) { + const auto parsed = + ParsedFormat<'v', 'u', 'c', 'v', 'f', 'v'>::NewAllowIgnored(formats[i]); + std::ostringstream oss; + oss << StreamFormat(*parsed, 123, 3, 49, + absl::string_view("multistreaming!!!"), 1.01, 1.01); + int fmt_result = + snprintf(&*buf.begin(), buf.size(), formats_for_buf[i].c_str(), // + 123, 3, 49, "multistreaming!!!", 1.01, 1.01); + ASSERT_TRUE(oss) << formats[i]; + ASSERT_TRUE(fmt_result >= 0 && static_cast(fmt_result) < buf.size()) + << fmt_result; + EXPECT_EQ(buf.c_str(), oss.str()); + } } TEST_F(FormatEntryPointTest, StreamOk) { @@ -238,6 +310,13 @@ TEST_F(FormatEntryPointTest, StreamOk) { EXPECT_TRUE(oss.good()); } +TEST_F(FormatEntryPointTest, StreamOkWithV) { + std::ostringstream oss; + oss << StreamFormat("hello %v", 123); + EXPECT_EQ("hello 123", oss.str()); + EXPECT_TRUE(oss.good()); +} + TEST_F(FormatEntryPointTest, StreamFail) { std::ostringstream oss; UntypedFormatSpec format("hello %d"); @@ -248,6 +327,16 @@ TEST_F(FormatEntryPointTest, StreamFail) { EXPECT_TRUE(oss.fail()); } +TEST_F(FormatEntryPointTest, StreamFailWithV) { + std::ostringstream oss; + UntypedFormatSpec format("hello %v"); + FormatArgImpl arg("non-numeric"); + oss << str_format_internal::Streamable( + str_format_internal::UntypedFormatSpecImpl::Extract(format), {&arg, 1}); + EXPECT_EQ("hello ", oss.str()); // partial write + EXPECT_TRUE(oss.fail()); +} + std::string WithSnprintf(const char* fmt, ...) { std::string buf; buf.resize(128); @@ -425,19 +514,19 @@ TEST_F(FormatEntryPointTest, SNPrintFWithV) { EXPECT_EQ(result, 11); EXPECT_EQ(std::string(buffer), "STRING: ABC"); - result = SNPrintF(buffer, sizeof(buffer), "NUMBER: %d", 123456); + result = SNPrintF(buffer, sizeof(buffer), "NUMBER: %v", 123456); EXPECT_EQ(result, 14); EXPECT_EQ(std::string(buffer), "NUMBER: 123456"); - result = SNPrintF(buffer, sizeof(buffer), "NUMBER: %d", 1234567); + result = SNPrintF(buffer, sizeof(buffer), "NUMBER: %v", 1234567); EXPECT_EQ(result, 15); EXPECT_EQ(std::string(buffer), "NUMBER: 1234567"); - result = SNPrintF(buffer, sizeof(buffer), "NUMBER: %d", 12345678); + result = SNPrintF(buffer, sizeof(buffer), "NUMBER: %v", 12345678); EXPECT_EQ(result, 16); EXPECT_EQ(std::string(buffer), "NUMBER: 1234567"); - result = SNPrintF(buffer, sizeof(buffer), "NUMBER: %d", 123456789); + result = SNPrintF(buffer, sizeof(buffer), "NUMBER: %v", 123456789); EXPECT_EQ(result, 17); EXPECT_EQ(std::string(buffer), "NUMBER: 1234567"); @@ -451,7 +540,7 @@ TEST(StrFormat, BehavesAsDocumented) { std::string s = absl::StrFormat("%s, %d!", "Hello", 123); EXPECT_EQ("Hello, 123!", s); std::string hello = "Hello"; - std::string s2 = absl::StrFormat("%v, %d!", hello, 123); + std::string s2 = absl::StrFormat("%v, %v!", hello, 123); EXPECT_EQ("Hello, 123!", s2); // The format of a replacement is // '%'[position][flags][width['.'precision]][length_modifier][format] @@ -480,13 +569,18 @@ TEST(StrFormat, BehavesAsDocumented) { EXPECT_EQ(StrFormat("%d", int{10}), "10"); EXPECT_EQ(StrFormat("%d", long{10}), "10"); // NOLINT EXPECT_EQ(StrFormat("%d", uint64_t{10}), "10"); + EXPECT_EQ(StrFormat("%v", int{10}), "10"); + EXPECT_EQ(StrFormat("%v", long{10}), "10"); // NOLINT + EXPECT_EQ(StrFormat("%v", uint64_t{10}), "10"); // d,i - signed decimal Eg: -10 -> "-10" EXPECT_EQ(StrFormat("%d", -10), "-10"); EXPECT_EQ(StrFormat("%i", -10), "-10"); + EXPECT_EQ(StrFormat("%v", -10), "-10"); // o - octal Eg: 10 -> "12" EXPECT_EQ(StrFormat("%o", 10), "12"); // u - unsigned decimal Eg: 10 -> "10" EXPECT_EQ(StrFormat("%u", 10), "10"); + EXPECT_EQ(StrFormat("%v", 10), "10"); // x/X - lower,upper case hex Eg: 10 -> "a"/"A" EXPECT_EQ(StrFormat("%x", 10), "a"); EXPECT_EQ(StrFormat("%X", 10), "A"); @@ -511,6 +605,8 @@ TEST(StrFormat, BehavesAsDocumented) { EXPECT_EQ(StrFormat("%g", .01), "0.01"); EXPECT_EQ(StrFormat("%g", 1e10), "1e+10"); EXPECT_EQ(StrFormat("%G", 1e10), "1E+10"); + EXPECT_EQ(StrFormat("%v", .01), "0.01"); + EXPECT_EQ(StrFormat("%v", 1e10), "1e+10"); // a/A - lower,upper case hex Eg: -3.0 -> "-0x1.8p+1"/"-0X1.8P+1" // On Android platform <=21, there is a regression in hexfloat formatting. @@ -598,10 +694,10 @@ TEST_F(ParsedFormatTest, SimpleChecked) { } TEST_F(ParsedFormatTest, SimpleCheckedWithV) { - EXPECT_EQ("[ABC]{d:1$d}[DEF]", - SummarizeParsedFormat(ParsedFormat<'d'>("ABC%dDEF"))); - EXPECT_EQ("{v:1$v}[FFF]{d:2$d}[ZZZ]{f:3$f}", - SummarizeParsedFormat(ParsedFormat<'v', 'd', 'f'>("%vFFF%dZZZ%f"))); + EXPECT_EQ("[ABC]{v:1$v}[DEF]", + SummarizeParsedFormat(ParsedFormat<'v'>("ABC%vDEF"))); + EXPECT_EQ("{v:1$v}[FFF]{v:2$v}[ZZZ]{f:3$f}", + SummarizeParsedFormat(ParsedFormat<'v', 'v', 'f'>("%vFFF%vZZZ%f"))); EXPECT_EQ("{v:1$v}[ ]{.*d:3$.2$*d}", SummarizeParsedFormat(ParsedFormat<'v', '*', 'd'>("%v %.*d"))); } @@ -637,20 +733,20 @@ TEST_F(ParsedFormatTest, SimpleUncheckedCorrect) { } TEST_F(ParsedFormatTest, SimpleUncheckedCorrectWithV) { - auto f = ParsedFormat<'d'>::New("ABC%dDEF"); + auto f = ParsedFormat<'v'>::New("ABC%vDEF"); ASSERT_TRUE(f); - EXPECT_EQ("[ABC]{d:1$d}[DEF]", SummarizeParsedFormat(*f)); + EXPECT_EQ("[ABC]{v:1$v}[DEF]", SummarizeParsedFormat(*f)); - std::string format = "%vFFF%dZZZ%f"; - auto f2 = ParsedFormat<'v', 'd', 'f'>::New(format); + std::string format = "%vFFF%vZZZ%f"; + auto f2 = ParsedFormat<'v', 'v', 'f'>::New(format); ASSERT_TRUE(f2); - EXPECT_EQ("{v:1$v}[FFF]{d:2$d}[ZZZ]{f:3$f}", SummarizeParsedFormat(*f2)); + EXPECT_EQ("{v:1$v}[FFF]{v:2$v}[ZZZ]{f:3$f}", SummarizeParsedFormat(*f2)); - f2 = ParsedFormat<'v', 'd', 'f'>::New("%v %d %f"); + f2 = ParsedFormat<'v', 'v', 'f'>::New("%v %v %f"); ASSERT_TRUE(f2); - EXPECT_EQ("{v:1$v}[ ]{d:2$d}[ ]{f:3$f}", SummarizeParsedFormat(*f2)); + EXPECT_EQ("{v:1$v}[ ]{v:2$v}[ ]{f:3$f}", SummarizeParsedFormat(*f2)); } TEST_F(ParsedFormatTest, SimpleUncheckedIgnoredArgs) { @@ -668,6 +764,18 @@ TEST_F(ParsedFormatTest, SimpleUncheckedIgnoredArgs) { EXPECT_EQ("[ABC]{2$s:2$s}", SummarizeParsedFormat(*f)); } +TEST_F(ParsedFormatTest, SimpleUncheckedIgnoredArgsWithV) { + EXPECT_FALSE((ParsedFormat<'v', 'v'>::New("ABC"))); + EXPECT_FALSE((ParsedFormat<'v', 'v'>::New("%vABC"))); + EXPECT_FALSE((ParsedFormat<'v', 's'>::New("ABC%2$s"))); + auto f = ParsedFormat<'v', 'v'>::NewAllowIgnored("ABC"); + ASSERT_TRUE(f); + EXPECT_EQ("[ABC]", SummarizeParsedFormat(*f)); + f = ParsedFormat<'v', 'v'>::NewAllowIgnored("%vABC"); + ASSERT_TRUE(f); + EXPECT_EQ("{v:1$v}[ABC]", SummarizeParsedFormat(*f)); +} + TEST_F(ParsedFormatTest, SimpleUncheckedUnsupported) { EXPECT_FALSE(ParsedFormat<'d'>::New("%1$d %1$x")); EXPECT_FALSE(ParsedFormat<'x'>::New("%1$d %1$x")); @@ -683,12 +791,12 @@ TEST_F(ParsedFormatTest, SimpleUncheckedIncorrect) { } TEST_F(ParsedFormatTest, SimpleUncheckedIncorrectWithV) { - EXPECT_FALSE(ParsedFormat<'d'>::New("")); + EXPECT_FALSE(ParsedFormat<'v'>::New("")); - EXPECT_FALSE(ParsedFormat<'d'>::New("ABC%dDEF%d")); + EXPECT_FALSE(ParsedFormat<'v'>::New("ABC%vDEF%v")); - std::string format = "%vFFF%dZZZ%f"; - EXPECT_FALSE((ParsedFormat<'v', 'd', 'g'>::New(format))); + std::string format = "%vFFF%vZZZ%f"; + EXPECT_FALSE((ParsedFormat<'v', 'v', 'g'>::New(format))); } #if defined(__cpp_nontype_template_parameter_auto) @@ -739,17 +847,17 @@ TEST_F(ParsedFormatTest, ExtendedTyping) { } TEST_F(ParsedFormatTest, ExtendedTypingWithV) { - EXPECT_FALSE(ParsedFormat::New("")); - ASSERT_TRUE(ParsedFormat::New("%d")); - auto v1 = ParsedFormat<'d', absl::FormatConversionCharSet::v>::New("%d%v"); + EXPECT_FALSE(ParsedFormat::New("")); + ASSERT_TRUE(ParsedFormat::New("%v")); + auto v1 = ParsedFormat<'v', absl::FormatConversionCharSet::v>::New("%v%v"); ASSERT_TRUE(v1); - auto v2 = ParsedFormat::New("%d%v"); + auto v2 = ParsedFormat::New("%v%v"); ASSERT_TRUE(v2); - auto v3 = ParsedFormat::New("%d%v"); + 'v'>::New("%v%v"); ASSERT_TRUE(v3); - auto v4 = ParsedFormat::New("%v%v"); ASSERT_TRUE(v4); @@ -799,24 +907,24 @@ TEST_F(ParsedFormatTest, UncheckedCorrect) { TEST_F(ParsedFormatTest, UncheckedCorrectWithV) { auto f = - ExtendedParsedFormat::New("ABC%dDEF"); + ExtendedParsedFormat::New("ABC%vDEF"); ASSERT_TRUE(f); - EXPECT_EQ("[ABC]{d:1$d}[DEF]", SummarizeParsedFormat(*f)); + EXPECT_EQ("[ABC]{v:1$v}[DEF]", SummarizeParsedFormat(*f)); - std::string format = "%vFFF%dZZZ%f"; + std::string format = "%vFFF%vZZZ%f"; auto f2 = ExtendedParsedFormat< - absl::FormatConversionCharSet::v, absl::FormatConversionCharSet::d, + absl::FormatConversionCharSet::v, absl::FormatConversionCharSet::v, absl::FormatConversionCharSet::kFloating>::New(format); ASSERT_TRUE(f2); - EXPECT_EQ("{v:1$v}[FFF]{d:2$d}[ZZZ]{f:3$f}", SummarizeParsedFormat(*f2)); + EXPECT_EQ("{v:1$v}[FFF]{v:2$v}[ZZZ]{f:3$f}", SummarizeParsedFormat(*f2)); f2 = ExtendedParsedFormat< - absl::FormatConversionCharSet::v, absl::FormatConversionCharSet::d, - absl::FormatConversionCharSet::kFloating>::New("%v %d %f"); + absl::FormatConversionCharSet::v, absl::FormatConversionCharSet::v, + absl::FormatConversionCharSet::kFloating>::New("%v %v %f"); ASSERT_TRUE(f2); - EXPECT_EQ("{v:1$v}[ ]{d:2$d}[ ]{f:3$f}", SummarizeParsedFormat(*f2)); + EXPECT_EQ("{v:1$v}[ ]{v:2$v}[ ]{f:3$f}", SummarizeParsedFormat(*f2)); } TEST_F(ParsedFormatTest, UncheckedIgnoredArgs) { @@ -846,6 +954,28 @@ TEST_F(ParsedFormatTest, UncheckedIgnoredArgs) { EXPECT_EQ("[ABC]{2$s:2$s}", SummarizeParsedFormat(*f)); } +TEST_F(ParsedFormatTest, UncheckedIgnoredArgsWithV) { + EXPECT_FALSE( + (ExtendedParsedFormat::New("ABC"))); + EXPECT_FALSE( + (ExtendedParsedFormat::New("%vABC"))); + EXPECT_FALSE((ExtendedParsedFormat:: + New("ABC%2$s"))); + auto f = ExtendedParsedFormat< + absl::FormatConversionCharSet::v, + absl::FormatConversionCharSet::v>::NewAllowIgnored("ABC"); + ASSERT_TRUE(f); + EXPECT_EQ("[ABC]", SummarizeParsedFormat(*f)); + f = ExtendedParsedFormat< + absl::FormatConversionCharSet::v, + absl::FormatConversionCharSet::v>::NewAllowIgnored("%vABC"); + ASSERT_TRUE(f); + EXPECT_EQ("{v:1$v}[ABC]", SummarizeParsedFormat(*f)); +} + TEST_F(ParsedFormatTest, UncheckedMultipleTypes) { auto dx = ExtendedParsedFormat::New("")); + EXPECT_FALSE(ExtendedParsedFormat::New("")); - EXPECT_FALSE(ExtendedParsedFormat::New( - "ABC%dDEF%d")); + EXPECT_FALSE(ExtendedParsedFormat::New( + "ABC%vDEF%v")); - std::string format = "%vFFF%dZZZ%f"; + std::string format = "%vFFF%vZZZ%f"; EXPECT_FALSE( (ExtendedParsedFormat::New(format))); } -- cgit v1.2.3 From 55996e2e30cd0dc3a73dbb233e8620fec1ff36bf Mon Sep 17 00:00:00 2001 From: Abseil Team Date: Tue, 20 Sep 2022 13:24:16 -0700 Subject: Explicitly disallows modifiers for use with %v. PiperOrigin-RevId: 475636693 Change-Id: Idb7c2b9c36ad8e59f24ff7df179a207d301d9e89 --- absl/strings/internal/str_format/parser.cc | 14 +++++++++----- absl/strings/str_format_test.cc | 11 +++++++++++ 2 files changed, 20 insertions(+), 5 deletions(-) (limited to 'absl/strings/str_format_test.cc') diff --git a/absl/strings/internal/str_format/parser.cc b/absl/strings/internal/str_format/parser.cc index 3d987334..f9bb6615 100644 --- a/absl/strings/internal/str_format/parser.cc +++ b/absl/strings/internal/str_format/parser.cc @@ -202,6 +202,10 @@ const char *ConsumeConversion(const char *pos, const char *const end, auto tag = GetTagForChar(c); + if (*(pos - 1) == 'v' && *(pos - 2) != '%') { + return nullptr; + } + if (ABSL_PREDICT_FALSE(!tag.is_conv())) { if (ABSL_PREDICT_FALSE(!tag.is_length())) return nullptr; @@ -312,11 +316,11 @@ bool ParsedFormatBase::MatchesConversions( std::initializer_list convs) const { std::unordered_set used; auto add_if_valid_conv = [&](int pos, char c) { - if (static_cast(pos) > convs.size() || - !Contains(convs.begin()[pos - 1], c)) - return false; - used.insert(pos); - return true; + if (static_cast(pos) > convs.size() || + !Contains(convs.begin()[pos - 1], c)) + return false; + used.insert(pos); + return true; }; for (const ConversionItem &item : items_) { if (!item.is_conversion) continue; diff --git a/absl/strings/str_format_test.cc b/absl/strings/str_format_test.cc index 9ef69837..2774cfed 100644 --- a/absl/strings/str_format_test.cc +++ b/absl/strings/str_format_test.cc @@ -1020,6 +1020,17 @@ TEST_F(ParsedFormatTest, RegressionMixPositional) { absl::FormatConversionCharSet::o>::New("%1$d %o"))); } +TEST_F(ParsedFormatTest, DisallowModifiersWithV) { + auto f = ParsedFormat<'v'>::New("ABC%80vDEF"); + EXPECT_EQ(f, nullptr); + + f = ParsedFormat<'v'>::New("ABC%0vDEF"); + EXPECT_EQ(f, nullptr); + + f = ParsedFormat<'v'>::New("ABC%.1vDEF"); + EXPECT_EQ(f, nullptr); +} + using FormatWrapperTest = ::testing::Test; // Plain wrapper for StrFormat. -- cgit v1.2.3 From 9398fa76a3436bfb89f7a6ec5c74a21f1e4d6029 Mon Sep 17 00:00:00 2001 From: Abseil Team Date: Tue, 20 Sep 2022 14:20:55 -0700 Subject: Adds support for "%v" in absl::StrFormat and related functions for bool values. Note that %v prints bool values as "true" and "false" rather than "1" and "0". PiperOrigin-RevId: 475651689 Change-Id: I24b3749d85b5bb7cbc18653d629271a8b2b07d08 --- absl/strings/internal/str_format/arg.cc | 11 ++++++++++- absl/strings/internal/str_format/arg.h | 9 +++++++++ absl/strings/str_format_test.cc | 7 ++++++- 3 files changed, 25 insertions(+), 2 deletions(-) (limited to 'absl/strings/str_format_test.cc') diff --git a/absl/strings/internal/str_format/arg.cc b/absl/strings/internal/str_format/arg.cc index b0e06bfb..967fe9ca 100644 --- a/absl/strings/internal/str_format/arg.cc +++ b/absl/strings/internal/str_format/arg.cc @@ -344,7 +344,7 @@ bool ConvertIntArg(T v, FormatConversionSpecImpl conv, FormatSinkImpl *sink) { return ConvertFloatImpl(static_cast(v), conv, sink); default: - ABSL_ASSUME(false); + ABSL_ASSUME(false); } if (conv.is_basic()) { @@ -376,6 +376,15 @@ inline bool ConvertStringArg(string_view v, const FormatConversionSpecImpl conv, } // namespace +bool ConvertBoolArg(bool v, FormatSinkImpl *sink) { + if (v) { + sink->Append("true"); + } else { + sink->Append("false"); + } + return true; +} + // ==================== Strings ==================== StringConvertResult FormatConvertImpl(const std::string &v, const FormatConversionSpecImpl conv, diff --git a/absl/strings/internal/str_format/arg.h b/absl/strings/internal/str_format/arg.h index 3c2bb3e6..2dfbf728 100644 --- a/absl/strings/internal/str_format/arg.h +++ b/absl/strings/internal/str_format/arg.h @@ -186,6 +186,8 @@ using CharConvertResult = ArgConvertResult; +bool ConvertBoolArg(bool v, FormatSinkImpl* sink); + // Floats. FloatingConvertResult FormatConvertImpl(float v, FormatConversionSpecImpl conv, FormatSinkImpl* sink); @@ -234,9 +236,16 @@ IntegralConvertResult FormatConvertImpl(int128 v, FormatConversionSpecImpl conv, IntegralConvertResult FormatConvertImpl(uint128 v, FormatConversionSpecImpl conv, FormatSinkImpl* sink); + +// This function needs to be a template due to ambiguity regarding type +// conversions. template ::value, int> = 0> IntegralConvertResult FormatConvertImpl(T v, FormatConversionSpecImpl conv, FormatSinkImpl* sink) { + if (conv.conversion_char() == FormatConversionCharInternal::v) { + return {ConvertBoolArg(v, sink)}; + } + return FormatConvertImpl(static_cast(v), conv, sink); } diff --git a/absl/strings/str_format_test.cc b/absl/strings/str_format_test.cc index 2774cfed..4b778056 100644 --- a/absl/strings/str_format_test.cc +++ b/absl/strings/str_format_test.cc @@ -265,7 +265,7 @@ TEST_F(FormatEntryPointTest, Stream) { std::ostringstream oss; oss << StreamFormat(*parsed, 123, 3, 49, "multistreaming!!!", 1.01, 1.01); int fmt_result = snprintf(&*buf.begin(), buf.size(), fmt.c_str(), // - 123, 3, 49, "multistreaming!!!", 1.01, 1.01); + 123, 3, 49, "multistreaming!!!", 1.01, 1.01); ASSERT_TRUE(oss) << fmt; ASSERT_TRUE(fmt_result >= 0 && static_cast(fmt_result) < buf.size()) << fmt_result; @@ -644,6 +644,11 @@ TEST(StrFormat, BehavesAsDocumented) { EXPECT_EQ(StrFormat("%zd", int{1}), "1"); EXPECT_EQ(StrFormat("%td", int{1}), "1"); EXPECT_EQ(StrFormat("%qd", int{1}), "1"); + + // Bool is handled correctly depending on whether %v is used + EXPECT_EQ(StrFormat("%v", true), "true"); + EXPECT_EQ(StrFormat("%v", false), "false"); + EXPECT_EQ(StrFormat("%d", true), "1"); } using str_format_internal::ExtendedParsedFormat; -- cgit v1.2.3 From f7404cd33f2457bd561fdcfbb024e43852c94bcf Mon Sep 17 00:00:00 2001 From: Abseil Team Date: Wed, 28 Sep 2022 11:33:29 -0700 Subject: Allows absl::StrFormat to accept types which implement AbslStringify() PiperOrigin-RevId: 477507777 Change-Id: I5ecde3163ca560ac8774034e55654774e36ad230 --- absl/strings/internal/str_format/arg.h | 34 ++++++++++++++++++++++++---------- absl/strings/str_format_test.cc | 19 ++++++++++++++++++- 2 files changed, 42 insertions(+), 11 deletions(-) (limited to 'absl/strings/str_format_test.cc') diff --git a/absl/strings/internal/str_format/arg.h b/absl/strings/internal/str_format/arg.h index 2dfbf728..3d616dba 100644 --- a/absl/strings/internal/str_format/arg.h +++ b/absl/strings/internal/str_format/arg.h @@ -45,6 +45,11 @@ class FormatConversionSpec; namespace str_format_internal { +template +struct ArgConvertResult { + bool value; +}; + template struct HasUserDefinedConvert : std::false_type {}; @@ -71,6 +76,20 @@ auto FormatConvertImpl(const T& v, FormatConversionSpecImpl conv, return AbslFormatConvert(v, fcs, &fs); } +void AbslStringify(); // Stops the lexical name lookup +template +auto FormatConvertImpl(const T& v, FormatConversionSpecImpl, + FormatSinkImpl* sink) + -> std::enable_if_t(), v))>::value, + ArgConvertResult> { + using FormatSinkT = + absl::enable_if_t; + auto fs = sink->Wrap(); + AbslStringify(fs, v); + return {true}; +} + template class StreamedWrapper; @@ -95,11 +114,6 @@ struct VoidPtr { uintptr_t value; }; -template -struct ArgConvertResult { - bool value; -}; - template constexpr FormatConversionCharSet ExtractCharSet(FormatConvertResult) { return C; @@ -316,11 +330,11 @@ struct FormatArgImplFriend { template constexpr FormatConversionCharSet ArgumentToConv() { - return absl::str_format_internal::ExtractCharSet( - decltype(str_format_internal::FormatConvertImpl( - std::declval(), - std::declval(), - std::declval())){}); + using ConvResult = decltype(str_format_internal::FormatConvertImpl( + std::declval(), + std::declval(), + std::declval())); + return absl::str_format_internal::ExtractCharSet(ConvResult{}); } // A type-erased handle to a format argument. diff --git a/absl/strings/str_format_test.cc b/absl/strings/str_format_test.cc index 4b778056..0c4f10c8 100644 --- a/absl/strings/str_format_test.cc +++ b/absl/strings/str_format_test.cc @@ -1074,7 +1074,8 @@ using FormatExtensionTest = ::testing::Test; struct Point { friend absl::FormatConvertResult + absl::FormatConversionCharSet::kIntegral | + absl::FormatConversionCharSet::v> AbslFormatConvert(const Point& p, const absl::FormatConversionSpec& spec, absl::FormatSink* s) { if (spec.conversion_char() == absl::FormatConversionChar::s) { @@ -1093,6 +1094,7 @@ TEST_F(FormatExtensionTest, AbslFormatConvertExample) { Point p; EXPECT_EQ(absl::StrFormat("a %s z", p), "a x=10 y=20 z"); EXPECT_EQ(absl::StrFormat("a %d z", p), "a 10,20 z"); + EXPECT_EQ(absl::StrFormat("a %v z", p), "a 10,20 z"); // Typed formatting will fail to compile an invalid format. // StrFormat("%f", p); // Does not compile. @@ -1101,6 +1103,21 @@ TEST_F(FormatExtensionTest, AbslFormatConvertExample) { // FormatUntyped will return false for bad character. EXPECT_FALSE(absl::FormatUntyped(&actual, f1, {absl::FormatArg(p)})); } + +struct PointStringify { + template + friend void AbslStringify(FormatSink& sink, const PointStringify& p) { + sink.Append(absl::StrCat("(", p.x, ", ", p.y, ")")); + } + + double x = 10.0; + double y = 20.0; +}; + +TEST_F(FormatExtensionTest, AbslStringifyExample) { + PointStringify p; + EXPECT_EQ(absl::StrFormat("a %v z", p), "a (10, 20) z"); +} } // namespace // Some codegen thunks that we can use to easily dump the generated assembly for -- cgit v1.2.3 From b3162b1da62711c663d0025e2eabeb83fd1f2728 Mon Sep 17 00:00:00 2001 From: Marcin Kowalczyk Date: Wed, 5 Oct 2022 07:42:50 -0700 Subject: Make sinks provided to `AbslStringify()` usable with `absl::Format()`. Such sinks must define ADL-callable `AbslFormatFlush()`. It can just forward to `Append()`. PiperOrigin-RevId: 479043790 Change-Id: I5d7d80ca1e17adf03b77726df8a52e2b4e9196ce --- absl/strings/BUILD.bazel | 3 +-- absl/strings/CMakeLists.txt | 1 + absl/strings/str_cat.h | 5 +++++ absl/strings/str_cat_test.cc | 18 ++++++++++++++++++ absl/strings/str_format.h | 5 +++++ absl/strings/str_format_test.cc | 17 +++++++++++++++++ 6 files changed, 47 insertions(+), 2 deletions(-) (limited to 'absl/strings/str_format_test.cc') diff --git a/absl/strings/BUILD.bazel b/absl/strings/BUILD.bazel index 5a40887e..5b12c010 100644 --- a/absl/strings/BUILD.bazel +++ b/absl/strings/BUILD.bazel @@ -963,8 +963,8 @@ cc_test( copts = ABSL_TEST_COPTS, visibility = ["//visibility:private"], deps = [ + ":str_format", ":strings", - "//absl/base:core_headers", "@com_google_googletest//:gtest_main", ], ) @@ -1164,7 +1164,6 @@ cc_test( ":cord", ":str_format", ":strings", - "//absl/base:core_headers", "@com_google_googletest//:gtest_main", ], ) diff --git a/absl/strings/CMakeLists.txt b/absl/strings/CMakeLists.txt index e1c2093a..01f86184 100644 --- a/absl/strings/CMakeLists.txt +++ b/absl/strings/CMakeLists.txt @@ -281,6 +281,7 @@ absl_cc_test( ${ABSL_TEST_COPTS} DEPS absl::strings + absl::str_format absl::core_headers GTest::gmock_main ) diff --git a/absl/strings/str_cat.h b/absl/strings/str_cat.h index 05728ab5..6ee88f14 100644 --- a/absl/strings/str_cat.h +++ b/absl/strings/str_cat.h @@ -85,6 +85,11 @@ class StringifySink { bool PutPaddedString(string_view v, int width, int precision, bool left); + // Support `absl::Format(&sink, format, args...)`. + friend void AbslFormatFlush(StringifySink* sink, absl::string_view v) { + sink->Append(v); + } + template friend string_view ExtractStringification(StringifySink& sink, const T& v); diff --git a/absl/strings/str_cat_test.cc b/absl/strings/str_cat_test.cc index 868b9bce..1b3b7ece 100644 --- a/absl/strings/str_cat_test.cc +++ b/absl/strings/str_cat_test.cc @@ -21,6 +21,7 @@ #include #include "gtest/gtest.h" +#include "absl/strings/str_format.h" #include "absl/strings/substitute.h" #ifdef __ANDROID__ @@ -632,4 +633,21 @@ TEST(StrCat, AbslStringifyExample) { EXPECT_EQ(absl::StrCat("a ", p, " z"), "a (10, 20) z"); } +struct PointStringifyUsingFormat { + template + friend void AbslStringify(FormatSink& sink, + const PointStringifyUsingFormat& p) { + absl::Format(&sink, "(%g, %g)", p.x, p.y); + } + + double x = 10.0; + double y = 20.0; +}; + +TEST(StrCat, AbslStringifyExampleUsingFormat) { + PointStringifyUsingFormat p; + EXPECT_EQ(absl::StrCat(p), "(10, 20)"); + EXPECT_EQ(absl::StrCat("a ", p, " z"), "a (10, 20) z"); +} + } // namespace diff --git a/absl/strings/str_format.h b/absl/strings/str_format.h index e6537ea0..ffbcb9af 100644 --- a/absl/strings/str_format.h +++ b/absl/strings/str_format.h @@ -789,6 +789,11 @@ class FormatSink { return sink_->PutPaddedString(v, width, precision, left); } + // Support `absl::Format(&sink, format, args...)`. + friend void AbslFormatFlush(FormatSink* sink, absl::string_view v) { + sink->Append(v); + } + private: friend str_format_internal::FormatSinkImpl; explicit FormatSink(str_format_internal::FormatSinkImpl* s) : sink_(s) {} diff --git a/absl/strings/str_format_test.cc b/absl/strings/str_format_test.cc index 0c4f10c8..62ed262d 100644 --- a/absl/strings/str_format_test.cc +++ b/absl/strings/str_format_test.cc @@ -1118,6 +1118,23 @@ TEST_F(FormatExtensionTest, AbslStringifyExample) { PointStringify p; EXPECT_EQ(absl::StrFormat("a %v z", p), "a (10, 20) z"); } + +struct PointStringifyUsingFormat { + template + friend void AbslStringify(FormatSink& sink, + const PointStringifyUsingFormat& p) { + absl::Format(&sink, "(%g, %g)", p.x, p.y); + } + + double x = 10.0; + double y = 20.0; +}; + +TEST_F(FormatExtensionTest, AbslStringifyExampleUsingFormat) { + PointStringifyUsingFormat p; + EXPECT_EQ(absl::StrFormat("a %v z", p), "a (10, 20) z"); +} + } // namespace // Some codegen thunks that we can use to easily dump the generated assembly for -- cgit v1.2.3 From db8cd47898082fa47bff41fd76575630132669d3 Mon Sep 17 00:00:00 2001 From: Abseil Team Date: Wed, 9 Nov 2022 17:53:22 -0800 Subject: Add support for enum types with AbslStringify PiperOrigin-RevId: 487394692 Change-Id: I55e9b57055483dc921e9773c3643ea9be4f9bdf6 --- absl/strings/internal/str_format/arg.h | 10 ++++++++-- absl/strings/str_cat.h | 3 ++- absl/strings/str_cat_test.cc | 12 ++++++++++++ absl/strings/str_format_test.cc | 12 ++++++++++++ 4 files changed, 34 insertions(+), 3 deletions(-) (limited to 'absl/strings/str_format_test.cc') diff --git a/absl/strings/internal/str_format/arg.h b/absl/strings/internal/str_format/arg.h index b3e4ff15..bc4cde96 100644 --- a/absl/strings/internal/str_format/arg.h +++ b/absl/strings/internal/str_format/arg.h @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -25,10 +26,12 @@ #include #include #include +#include #include "absl/base/port.h" #include "absl/meta/type_traits.h" #include "absl/numeric/int128.h" +#include "absl/strings/internal/has_absl_stringify.h" #include "absl/strings/internal/str_format/extension.h" #include "absl/strings/string_view.h" @@ -271,7 +274,8 @@ IntegralConvertResult FormatConvertImpl(T v, FormatConversionSpecImpl conv, // FormatArgImpl will use the underlying Convert functions instead. template typename std::enable_if::value && - !HasUserDefinedConvert::value, + !HasUserDefinedConvert::value && + !strings_internal::HasAbslStringify::value, IntegralConvertResult>::type FormatConvertImpl(T v, FormatConversionSpecImpl conv, FormatSinkImpl* sink); @@ -384,7 +388,8 @@ class FormatArgImpl { template struct DecayType { static constexpr bool kHasUserDefined = - str_format_internal::HasUserDefinedConvert::value; + str_format_internal::HasUserDefinedConvert::value || + strings_internal::HasAbslStringify::value; using type = typename std::conditional< !kHasUserDefined && std::is_convertible::value, const char*, @@ -396,6 +401,7 @@ class FormatArgImpl { struct DecayType::value && + !strings_internal::HasAbslStringify::value && std::is_enum::value>::type> { using type = typename std::underlying_type::type; }; diff --git a/absl/strings/str_cat.h b/absl/strings/str_cat.h index 00af84ea..5ee26db0 100644 --- a/absl/strings/str_cat.h +++ b/absl/strings/str_cat.h @@ -318,7 +318,8 @@ class AlphaNum { // This overload matches only scoped enums. template {} && !std::is_convertible{}>::type> + std::is_enum{} && !std::is_convertible{} && + !strings_internal::HasAbslStringify::value>::type> AlphaNum(T e) // NOLINT(runtime/explicit) : AlphaNum(static_cast::type>(e)) {} diff --git a/absl/strings/str_cat_test.cc b/absl/strings/str_cat_test.cc index 1b3b7ece..c3fb3170 100644 --- a/absl/strings/str_cat_test.cc +++ b/absl/strings/str_cat_test.cc @@ -650,4 +650,16 @@ TEST(StrCat, AbslStringifyExampleUsingFormat) { EXPECT_EQ(absl::StrCat("a ", p, " z"), "a (10, 20) z"); } +enum class EnumWithStringify { Many = 0, Choices = 1 }; + +template +void AbslStringify(Sink& sink, EnumWithStringify e) { + absl::Format(&sink, "%s", e == EnumWithStringify::Many ? "Many" : "Choices"); +} + +TEST(StrCat, AbslStringifyWithEnum) { + const auto e = EnumWithStringify::Choices; + EXPECT_EQ(absl::StrCat(e), "Choices"); +} + } // namespace diff --git a/absl/strings/str_format_test.cc b/absl/strings/str_format_test.cc index 62ed262d..2aa22b0d 100644 --- a/absl/strings/str_format_test.cc +++ b/absl/strings/str_format_test.cc @@ -1135,6 +1135,18 @@ TEST_F(FormatExtensionTest, AbslStringifyExampleUsingFormat) { EXPECT_EQ(absl::StrFormat("a %v z", p), "a (10, 20) z"); } +enum class EnumWithStringify { Many = 0, Choices = 1 }; + +template +void AbslStringify(Sink& sink, EnumWithStringify e) { + absl::Format(&sink, "%s", e == EnumWithStringify::Many ? "Many" : "Choices"); +} + +TEST_F(FormatExtensionTest, AbslStringifyWithEnum) { + const auto e = EnumWithStringify::Choices; + EXPECT_EQ(absl::StrFormat("My choice is %v", e), "My choice is Choices"); +} + } // namespace // Some codegen thunks that we can use to easily dump the generated assembly for -- cgit v1.2.3 From 82196f059f213c50738142a799bb166b2971950d Mon Sep 17 00:00:00 2001 From: Samuel Benzaquen Date: Tue, 29 Nov 2022 08:57:48 -0800 Subject: Convert the full parser into constexpr now that Abseil requires C++14, and use this parser for the static checker. This fixes some outstanding bugs where the static checker differed from the dynamic one. Also, fix `%v` to be accepted with POSIX syntax. Tested: Presubmit TGP OCL:487237262:BASE:490275393:1669141454896:92dd62e3 PiperOrigin-RevId: 491650577 Change-Id: Id138c108187428b3aea46f8887495f1da12c91b2 --- CMake/AbseilDll.cmake | 1 + absl/strings/BUILD.bazel | 1 + absl/strings/CMakeLists.txt | 1 + absl/strings/internal/str_format/checker.h | 356 +++------------------ absl/strings/internal/str_format/checker_test.cc | 12 +- .../strings/internal/str_format/constexpr_parser.h | 351 ++++++++++++++++++++ absl/strings/internal/str_format/parser.cc | 213 +----------- absl/strings/internal/str_format/parser.h | 103 +----- absl/strings/internal/str_format/parser_test.cc | 1 + absl/strings/str_format_test.cc | 21 +- 10 files changed, 429 insertions(+), 631 deletions(-) create mode 100644 absl/strings/internal/str_format/constexpr_parser.h (limited to 'absl/strings/str_format_test.cc') diff --git a/CMake/AbseilDll.cmake b/CMake/AbseilDll.cmake index 217f884e..4565f4a8 100644 --- a/CMake/AbseilDll.cmake +++ b/CMake/AbseilDll.cmake @@ -295,6 +295,7 @@ set(ABSL_INTERNAL_DLL_FILES "strings/internal/str_format/bind.cc" "strings/internal/str_format/bind.h" "strings/internal/str_format/checker.h" + "strings/internal/str_format/constexpr_parser.h" "strings/internal/str_format/extension.cc" "strings/internal/str_format/extension.h" "strings/internal/str_format/float_conversion.cc" diff --git a/absl/strings/BUILD.bazel b/absl/strings/BUILD.bazel index 12a8d155..3c852989 100644 --- a/absl/strings/BUILD.bazel +++ b/absl/strings/BUILD.bazel @@ -1150,6 +1150,7 @@ cc_library( "internal/str_format/arg.h", "internal/str_format/bind.h", "internal/str_format/checker.h", + "internal/str_format/constexpr_parser.h", "internal/str_format/extension.h", "internal/str_format/float_conversion.h", "internal/str_format/output.h", diff --git a/absl/strings/CMakeLists.txt b/absl/strings/CMakeLists.txt index 7e91ebf2..33e4ff1b 100644 --- a/absl/strings/CMakeLists.txt +++ b/absl/strings/CMakeLists.txt @@ -413,6 +413,7 @@ absl_cc_library( "internal/str_format/arg.h" "internal/str_format/bind.h" "internal/str_format/checker.h" + "internal/str_format/constexpr_parser.h" "internal/str_format/extension.h" "internal/str_format/float_conversion.h" "internal/str_format/output.h" diff --git a/absl/strings/internal/str_format/checker.h b/absl/strings/internal/str_format/checker.h index aeb9d48d..eab6ab9d 100644 --- a/absl/strings/internal/str_format/checker.h +++ b/absl/strings/internal/str_format/checker.h @@ -15,8 +15,11 @@ #ifndef ABSL_STRINGS_INTERNAL_STR_FORMAT_CHECKER_H_ #define ABSL_STRINGS_INTERNAL_STR_FORMAT_CHECKER_H_ +#include + #include "absl/base/attributes.h" #include "absl/strings/internal/str_format/arg.h" +#include "absl/strings/internal/str_format/constexpr_parser.h" #include "absl/strings/internal/str_format/extension.h" // Compile time check support for entry points. @@ -36,333 +39,56 @@ namespace absl { ABSL_NAMESPACE_BEGIN namespace str_format_internal { -constexpr bool AllOf() { return true; } - -template -constexpr bool AllOf(bool b, T... t) { - return b && AllOf(t...); -} - #ifdef ABSL_INTERNAL_ENABLE_FORMAT_CHECKER -constexpr bool ContainsChar(const char* chars, char c) { - return *chars == c || (*chars && ContainsChar(chars + 1, c)); -} - -// A constexpr compatible list of Convs. -struct ConvList { - const FormatConversionCharSet* array; - int count; - - // We do the bound check here to avoid having to do it on the callers. - // Returning an empty FormatConversionCharSet has the same effect as - // short circuiting because it will never match any conversion. - constexpr FormatConversionCharSet operator[](int i) const { - return i < count ? array[i] : FormatConversionCharSet{}; - } - - constexpr ConvList without_front() const { - return count != 0 ? ConvList{array + 1, count - 1} : *this; - } -}; - -template -struct ConvListT { - // Make sure the array has size > 0. - FormatConversionCharSet list[count ? count : 1]; -}; - -constexpr char GetChar(string_view str, size_t index) { - return index < str.size() ? str[index] : char{}; -} - -constexpr string_view ConsumeFront(string_view str, size_t len = 1) { - return len <= str.size() ? string_view(str.data() + len, str.size() - len) - : string_view(); -} - -constexpr string_view ConsumeAnyOf(string_view format, const char* chars) { - while (ContainsChar(chars, GetChar(format, 0))) { - format = ConsumeFront(format); - } - return format; -} - -constexpr bool IsDigit(char c) { return c >= '0' && c <= '9'; } - -// Helper class for the ParseDigits function. -// It encapsulates the two return values we need there. -struct Integer { - string_view format; - int value; - - // If the next character is a '$', consume it. - // Otherwise, make `this` an invalid positional argument. - constexpr Integer ConsumePositionalDollar() const { - if (GetChar(format, 0) == '$') { - return Integer{ConsumeFront(format), value}; - } else { - return Integer{format, 0}; - } - } -}; - -constexpr Integer ParseDigits(string_view format) { - int value = 0; - while (IsDigit(GetChar(format, 0))) { - value = 10 * value + GetChar(format, 0) - '0'; - format = ConsumeFront(format); - } - - return Integer{format, value}; -} - -// Parse digits for a positional argument. -// The parsing also consumes the '$'. -constexpr Integer ParsePositional(string_view format) { - return ParseDigits(format).ConsumePositionalDollar(); -} - -// Parses a single conversion specifier. -// See ConvParser::Run() for post conditions. -class ConvParser { - constexpr ConvParser SetFormat(string_view format) const { - return ConvParser(format, args_, error_, arg_position_, is_positional_); - } - - constexpr ConvParser SetArgs(ConvList args) const { - return ConvParser(format_, args, error_, arg_position_, is_positional_); - } - - constexpr ConvParser SetError(bool error) const { - return ConvParser(format_, args_, error_ || error, arg_position_, - is_positional_); - } - - constexpr ConvParser SetArgPosition(int arg_position) const { - return ConvParser(format_, args_, error_, arg_position, is_positional_); - } - - // Consumes the next arg and verifies that it matches `conv`. - // `error_` is set if there is no next arg or if it doesn't match `conv`. - constexpr ConvParser ConsumeNextArg(char conv) const { - return SetArgs(args_.without_front()).SetError(!Contains(args_[0], conv)); - } - - // Verify that positional argument `i.value` matches `conv`. - // `error_` is set if `i.value` is not a valid argument or if it doesn't - // match. - constexpr ConvParser VerifyPositional(Integer i, char conv) const { - return SetFormat(i.format).SetError(!Contains(args_[i.value - 1], conv)); - } - - // Parse the position of the arg and store it in `arg_position_`. - constexpr ConvParser ParseArgPosition(Integer arg) const { - return SetFormat(arg.format).SetArgPosition(arg.value); - } - - // Consume the flags. - constexpr ConvParser ParseFlags() const { - return SetFormat(ConsumeAnyOf(format_, "-+ #0")); - } - - // Consume the width. - // If it is '*', we verify that it matches `args_`. `error_` is set if it - // doesn't match. - constexpr ConvParser ParseWidth() const { - char first_char = GetChar(format_, 0); - - if (IsDigit(first_char)) { - return SetFormat(ParseDigits(format_).format); - } else if (first_char == '*') { - if (is_positional_) { - return VerifyPositional(ParsePositional(ConsumeFront(format_)), '*'); - } else { - return SetFormat(ConsumeFront(format_)).ConsumeNextArg('*'); - } - } else { - return *this; +template +constexpr bool ValidFormatImpl(string_view format) { + int next_arg = 0; + const char* p = format.data(); + const char* const end = p + format.size(); + constexpr FormatConversionCharSet + kAllowedConvs[(std::max)(sizeof...(C), size_t{1})] = {C...}; + bool used[(std::max)(sizeof...(C), size_t{1})]{}; + constexpr int kNumArgs = sizeof...(C); + while (p != end) { + while (p != end && *p != '%') ++p; + if (p == end) { + break; } - } - - // Consume the precision. - // If it is '*', we verify that it matches `args_`. `error_` is set if it - // doesn't match. - constexpr ConvParser ParsePrecision() const { - if (GetChar(format_, 0) != '.') { - return *this; - } else if (GetChar(format_, 1) == '*') { - if (is_positional_) { - return VerifyPositional(ParsePositional(ConsumeFront(format_, 2)), '*'); - } else { - return SetFormat(ConsumeFront(format_, 2)).ConsumeNextArg('*'); - } - } else { - return SetFormat(ParseDigits(ConsumeFront(format_)).format); + if (p + 1 >= end) return false; + if (p[1] == '%') { + // %% + p += 2; + continue; } - } - - // Consume the length characters. - constexpr ConvParser ParseLength() const { - return SetFormat(ConsumeAnyOf(format_, "lLhjztq")); - } - - // Consume the conversion character and verify that it matches `args_`. - // `error_` is set if it doesn't match. - constexpr ConvParser ParseConversion() const { - char first_char = GetChar(format_, 0); - if (first_char == 'v' && *(format_.data() - 1) != '%') { - return SetError(true); + UnboundConversion conv(absl::kConstInit); + p = ConsumeUnboundConversion(p + 1, end, &conv, &next_arg); + if (p == nullptr) return false; + if (conv.arg_position <= 0 || conv.arg_position > kNumArgs) { + return false; } - - if (is_positional_) { - return VerifyPositional({ConsumeFront(format_), arg_position_}, - first_char); - } else { - return ConsumeNextArg(first_char).SetFormat(ConsumeFront(format_)); + if (!Contains(kAllowedConvs[conv.arg_position - 1], conv.conv)) { + return false; } - } - - constexpr ConvParser(string_view format, ConvList args, bool error, - int arg_position, bool is_positional) - : format_(format), - args_(args), - error_(error), - arg_position_(arg_position), - is_positional_(is_positional) {} - - public: - constexpr ConvParser(string_view format, ConvList args, bool is_positional) - : format_(format), - args_(args), - error_(false), - arg_position_(0), - is_positional_(is_positional) {} - - // Consume the whole conversion specifier. - // `format()` will be set to the character after the conversion character. - // `error()` will be set if any of the arguments do not match. - constexpr ConvParser Run() const { - ConvParser parser = *this; - - if (is_positional_) { - parser = ParseArgPosition(ParsePositional(format_)); - } - - return parser.ParseFlags() - .ParseWidth() - .ParsePrecision() - .ParseLength() - .ParseConversion(); - } - - constexpr string_view format() const { return format_; } - constexpr ConvList args() const { return args_; } - constexpr bool error() const { return error_; } - constexpr bool is_positional() const { return is_positional_; } - - private: - string_view format_; - // Current list of arguments. If we are not in positional mode we will consume - // from the front. - ConvList args_; - bool error_; - // Holds the argument position of the conversion character, if we are in - // positional mode. Otherwise, it is unspecified. - int arg_position_; - // Whether we are in positional mode. - // It changes the behavior of '*' and where to find the converted argument. - bool is_positional_; -}; - -// Parses a whole format expression. -// See FormatParser::Run(). -class FormatParser { - static constexpr bool FoundPercent(string_view format) { - return format.empty() || - (GetChar(format, 0) == '%' && GetChar(format, 1) != '%'); - } - - // We use an inner function to increase the recursion limit. - // The inner function consumes up to `limit` characters on every run. - // This increases the limit from 512 to ~512*limit. - static constexpr string_view ConsumeNonPercentInner(string_view format) { - int limit = 20; - while (!FoundPercent(format) && limit != 0) { - size_t len = 0; - - if (GetChar(format, 0) == '%' && GetChar(format, 1) == '%') { - len = 2; - } else { - len = 1; + used[conv.arg_position - 1] = true; + for (auto extra : {conv.width, conv.precision}) { + if (extra.is_from_arg()) { + int pos = extra.get_from_arg(); + if (pos <= 0 || pos > kNumArgs) return false; + used[pos - 1] = true; + if (!Contains(kAllowedConvs[pos - 1], '*')) { + return false; + } } - - format = ConsumeFront(format, len); - --limit; } - - return format; } - - // Consume characters until the next conversion spec %. - // It skips %%. - static constexpr string_view ConsumeNonPercent(string_view format) { - while (!FoundPercent(format)) { - format = ConsumeNonPercentInner(format); + if (sizeof...(C) != 0) { + for (bool b : used) { + if (!b) return false; } - - return format; - } - - static constexpr bool IsPositional(string_view format) { - while (IsDigit(GetChar(format, 0))) { - format = ConsumeFront(format); - } - - return GetChar(format, 0) == '$'; } - - constexpr bool RunImpl(bool is_positional) const { - // In non-positional mode we require all arguments to be consumed. - // In positional mode just reaching the end of the format without errors is - // enough. - return (format_.empty() && (is_positional || args_.count == 0)) || - (!format_.empty() && - ValidateArg( - ConvParser(ConsumeFront(format_), args_, is_positional).Run())); - } - - constexpr bool ValidateArg(ConvParser conv) const { - return !conv.error() && FormatParser(conv.format(), conv.args()) - .RunImpl(conv.is_positional()); - } - - public: - constexpr FormatParser(string_view format, ConvList args) - : format_(ConsumeNonPercent(format)), args_(args) {} - - // Runs the parser for `format` and `args`. - // It verifies that the format is valid and that all conversion specifiers - // match the arguments passed. - // In non-positional mode it also verfies that all arguments are consumed. - constexpr bool Run() const { - return RunImpl(!format_.empty() && IsPositional(ConsumeFront(format_))); - } - - private: - string_view format_; - // Current list of arguments. - // If we are not in positional mode we will consume from the front and will - // have to be empty in the end. - ConvList args_; -}; - -template -constexpr bool ValidFormatImpl(string_view format) { - return FormatParser(format, - {ConvListT{{C...}}.list, sizeof...(C)}) - .Run(); + return true; } #endif // ABSL_INTERNAL_ENABLE_FORMAT_CHECKER diff --git a/absl/strings/internal/str_format/checker_test.cc b/absl/strings/internal/str_format/checker_test.cc index 680517f7..a86bed38 100644 --- a/absl/strings/internal/str_format/checker_test.cc +++ b/absl/strings/internal/str_format/checker_test.cc @@ -93,6 +93,7 @@ TEST(StrFormatChecker, ValidFormat) { ValidFormat("%p %p"), // ValidFormat( "string_view=%s const char*=%s double=%f void*=%p)"), + ValidFormat("%v"), // ValidFormat("%% %1$d"), // ValidFormat("%1$ld"), // @@ -109,7 +110,9 @@ TEST(StrFormatChecker, ValidFormat) { ValidFormat("%2$.*1$f"), // ValidFormat( "string_view=%2$s const char*=%3$s double=%4$f void*=%1$p " - "repeat=%3$s)")}; + "repeat=%3$s)"), + ValidFormat("%1$v"), + }; for (Case c : trues) { EXPECT_TRUE(c.result) << c.format; @@ -130,6 +133,8 @@ TEST(StrFormatChecker, ValidFormat) { ValidFormat("%*d"), // ValidFormat("%p"), // ValidFormat("%d"), // + ValidFormat("%1v"), // + ValidFormat("%.1v"), // ValidFormat<>("%3$d"), // ValidFormat<>("%1$r"), // @@ -138,13 +143,14 @@ TEST(StrFormatChecker, ValidFormat) { ValidFormat("%1$*2$1d"), // ValidFormat("%1$1-d"), // ValidFormat("%2$*1$s"), // - ValidFormat("%1$p"), + ValidFormat("%1$p"), // + ValidFormat("%1$*2$v"), // ValidFormat("%d %2$d"), // }; for (Case c : falses) { - EXPECT_FALSE(c.result) << c.format; + EXPECT_FALSE(c.result) << "format<" << c.format << ">"; } } diff --git a/absl/strings/internal/str_format/constexpr_parser.h b/absl/strings/internal/str_format/constexpr_parser.h new file mode 100644 index 00000000..3dc1776b --- /dev/null +++ b/absl/strings/internal/str_format/constexpr_parser.h @@ -0,0 +1,351 @@ +// Copyright 2022 The Abseil Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef ABSL_STRINGS_INTERNAL_STR_FORMAT_CONSTEXPR_PARSER_H_ +#define ABSL_STRINGS_INTERNAL_STR_FORMAT_CONSTEXPR_PARSER_H_ + +#include +#include +#include + +#include "absl/base/const_init.h" +#include "absl/strings/internal/str_format/extension.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace str_format_internal { + +enum class LengthMod : std::uint8_t { h, hh, l, ll, L, j, z, t, q, none }; + +// The analyzed properties of a single specified conversion. +struct UnboundConversion { + // This is a user defined default constructor on purpose to skip the + // initialization of parts of the object that are not necessary. + UnboundConversion() {} // NOLINT + + // This constructor is provided for the static checker. We don't want to do + // the unnecessary initialization in the normal case. + explicit constexpr UnboundConversion(absl::ConstInitType) + : arg_position{}, width{}, precision{} {} + + class InputValue { + public: + constexpr void set_value(int value) { + assert(value >= 0); + value_ = value; + } + constexpr int value() const { return value_; } + + // Marks the value as "from arg". aka the '*' format. + // Requires `value >= 1`. + // When set, is_from_arg() return true and get_from_arg() returns the + // original value. + // `value()`'s return value is unspecified in this state. + constexpr void set_from_arg(int value) { + assert(value > 0); + value_ = -value - 1; + } + constexpr bool is_from_arg() const { return value_ < -1; } + constexpr int get_from_arg() const { + assert(is_from_arg()); + return -value_ - 1; + } + + private: + int value_ = -1; + }; + + // No need to initialize. It will always be set in the parser. + int arg_position; + + InputValue width; + InputValue precision; + + Flags flags = Flags::kBasic; + LengthMod length_mod = LengthMod::none; + FormatConversionChar conv = FormatConversionCharInternal::kNone; +}; + +// Helper tag class for the table below. +// It allows fast `char -> ConversionChar/LengthMod/Flags` checking and +// conversions. +class ConvTag { + public: + constexpr ConvTag(FormatConversionChar conversion_char) // NOLINT + : tag_(static_cast(conversion_char)) {} + constexpr ConvTag(LengthMod length_mod) // NOLINT + : tag_(0x80 | static_cast(length_mod)) {} + constexpr ConvTag(Flags flags) // NOLINT + : tag_(0xc0 | static_cast(flags)) {} + constexpr ConvTag() : tag_(0xFF) {} + + constexpr bool is_conv() const { return (tag_ & 0x80) == 0; } + constexpr bool is_length() const { return (tag_ & 0xC0) == 0x80; } + constexpr bool is_flags() const { return (tag_ & 0xE0) == 0xC0; } + + constexpr FormatConversionChar as_conv() const { + assert(is_conv()); + assert(!is_length()); + assert(!is_flags()); + return static_cast(tag_); + } + constexpr LengthMod as_length() const { + assert(!is_conv()); + assert(is_length()); + assert(!is_flags()); + return static_cast(tag_ & 0x3F); + } + constexpr Flags as_flags() const { + assert(!is_conv()); + assert(!is_length()); + assert(is_flags()); + return static_cast(tag_ & 0x1F); + } + + private: + uint8_t tag_; +}; + +struct ConvTagHolder { + using CC = FormatConversionCharInternal; + using LM = LengthMod; + + // Abbreviations to fit in the table below. + static constexpr auto kFSign = Flags::kSignCol; + static constexpr auto kFAlt = Flags::kAlt; + static constexpr auto kFPos = Flags::kShowPos; + static constexpr auto kFLeft = Flags::kLeft; + static constexpr auto kFZero = Flags::kZero; + + static constexpr ConvTag value[256] = { + {}, {}, {}, {}, {}, {}, {}, {}, // 00-07 + {}, {}, {}, {}, {}, {}, {}, {}, // 08-0f + {}, {}, {}, {}, {}, {}, {}, {}, // 10-17 + {}, {}, {}, {}, {}, {}, {}, {}, // 18-1f + kFSign, {}, {}, kFAlt, {}, {}, {}, {}, // !"#$%&' + {}, {}, {}, kFPos, {}, kFLeft, {}, {}, // ()*+,-./ + kFZero, {}, {}, {}, {}, {}, {}, {}, // 01234567 + {}, {}, {}, {}, {}, {}, {}, {}, // 89:;<=>? + {}, CC::A, {}, {}, {}, CC::E, CC::F, CC::G, // @ABCDEFG + {}, {}, {}, {}, LM::L, {}, {}, {}, // HIJKLMNO + {}, {}, {}, {}, {}, {}, {}, {}, // PQRSTUVW + CC::X, {}, {}, {}, {}, {}, {}, {}, // XYZ[\]^_ + {}, CC::a, {}, CC::c, CC::d, CC::e, CC::f, CC::g, // `abcdefg + LM::h, CC::i, LM::j, {}, LM::l, {}, CC::n, CC::o, // hijklmno + CC::p, LM::q, {}, CC::s, LM::t, CC::u, CC::v, {}, // pqrstuvw + CC::x, {}, LM::z, {}, {}, {}, {}, {}, // xyz{|}! + {}, {}, {}, {}, {}, {}, {}, {}, // 80-87 + {}, {}, {}, {}, {}, {}, {}, {}, // 88-8f + {}, {}, {}, {}, {}, {}, {}, {}, // 90-97 + {}, {}, {}, {}, {}, {}, {}, {}, // 98-9f + {}, {}, {}, {}, {}, {}, {}, {}, // a0-a7 + {}, {}, {}, {}, {}, {}, {}, {}, // a8-af + {}, {}, {}, {}, {}, {}, {}, {}, // b0-b7 + {}, {}, {}, {}, {}, {}, {}, {}, // b8-bf + {}, {}, {}, {}, {}, {}, {}, {}, // c0-c7 + {}, {}, {}, {}, {}, {}, {}, {}, // c8-cf + {}, {}, {}, {}, {}, {}, {}, {}, // d0-d7 + {}, {}, {}, {}, {}, {}, {}, {}, // d8-df + {}, {}, {}, {}, {}, {}, {}, {}, // e0-e7 + {}, {}, {}, {}, {}, {}, {}, {}, // e8-ef + {}, {}, {}, {}, {}, {}, {}, {}, // f0-f7 + {}, {}, {}, {}, {}, {}, {}, {}, // f8-ff + }; +}; + +// Keep a single table for all the conversion chars and length modifiers. +constexpr ConvTag GetTagForChar(char c) { + return ConvTagHolder::value[static_cast(c)]; +} + +constexpr bool CheckFastPathSetting(const UnboundConversion& conv) { + bool width_precision_needed = + conv.width.value() >= 0 || conv.precision.value() >= 0; + if (width_precision_needed && conv.flags == Flags::kBasic) { +#if defined(__clang__) + // Some compilers complain about this in constexpr even when not executed, + // so only enable the error dump in clang. + fprintf(stderr, + "basic=%d left=%d show_pos=%d sign_col=%d alt=%d zero=%d " + "width=%d precision=%d\n", + conv.flags == Flags::kBasic ? 1 : 0, + FlagsContains(conv.flags, Flags::kLeft) ? 1 : 0, + FlagsContains(conv.flags, Flags::kShowPos) ? 1 : 0, + FlagsContains(conv.flags, Flags::kSignCol) ? 1 : 0, + FlagsContains(conv.flags, Flags::kAlt) ? 1 : 0, + FlagsContains(conv.flags, Flags::kZero) ? 1 : 0, conv.width.value(), + conv.precision.value()); +#endif // defined(__clang__) + return false; + } + return true; +} + +constexpr int ParseDigits(char& c, const char*& pos, const char* const end) { + int digits = c - '0'; + // We do not want to overflow `digits` so we consume at most digits10 + // digits. If there are more digits the parsing will fail later on when the + // digit doesn't match the expected characters. + int num_digits = std::numeric_limits::digits10; + for (;;) { + if (ABSL_PREDICT_FALSE(pos == end)) break; + c = *pos++; + if ('0' > c || c > '9') break; + --num_digits; + if (ABSL_PREDICT_FALSE(!num_digits)) break; + digits = 10 * digits + c - '0'; + } + return digits; +} + +template +constexpr const char* ConsumeConversion(const char* pos, const char* const end, + UnboundConversion* conv, + int* next_arg) { + const char* const original_pos = pos; + char c = 0; + // Read the next char into `c` and update `pos`. Returns false if there are + // no more chars to read. +#define ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR() \ + do { \ + if (ABSL_PREDICT_FALSE(pos == end)) return nullptr; \ + c = *pos++; \ + } while (0) + + if (is_positional) { + ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); + if (ABSL_PREDICT_FALSE(c < '1' || c > '9')) return nullptr; + conv->arg_position = ParseDigits(c, pos, end); + assert(conv->arg_position > 0); + if (ABSL_PREDICT_FALSE(c != '$')) return nullptr; + } + + ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); + + // We should start with the basic flag on. + assert(conv->flags == Flags::kBasic); + + // Any non alpha character makes this conversion not basic. + // This includes flags (-+ #0), width (1-9, *) or precision (.). + // All conversion characters and length modifiers are alpha characters. + if (c < 'A') { + while (c <= '0') { + auto tag = GetTagForChar(c); + if (tag.is_flags()) { + conv->flags = conv->flags | tag.as_flags(); + ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); + } else { + break; + } + } + + if (c <= '9') { + if (c >= '0') { + int maybe_width = ParseDigits(c, pos, end); + if (!is_positional && c == '$') { + if (ABSL_PREDICT_FALSE(*next_arg != 0)) return nullptr; + // Positional conversion. + *next_arg = -1; + return ConsumeConversion(original_pos, end, conv, next_arg); + } + conv->flags = conv->flags | Flags::kNonBasic; + conv->width.set_value(maybe_width); + } else if (c == '*') { + conv->flags = conv->flags | Flags::kNonBasic; + ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); + if (is_positional) { + if (ABSL_PREDICT_FALSE(c < '1' || c > '9')) return nullptr; + conv->width.set_from_arg(ParseDigits(c, pos, end)); + if (ABSL_PREDICT_FALSE(c != '$')) return nullptr; + ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); + } else { + conv->width.set_from_arg(++*next_arg); + } + } + } + + if (c == '.') { + conv->flags = conv->flags | Flags::kNonBasic; + ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); + if ('0' <= c && c <= '9') { + conv->precision.set_value(ParseDigits(c, pos, end)); + } else if (c == '*') { + ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); + if (is_positional) { + if (ABSL_PREDICT_FALSE(c < '1' || c > '9')) return nullptr; + conv->precision.set_from_arg(ParseDigits(c, pos, end)); + if (c != '$') return nullptr; + ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); + } else { + conv->precision.set_from_arg(++*next_arg); + } + } else { + conv->precision.set_value(0); + } + } + } + + auto tag = GetTagForChar(c); + + if (ABSL_PREDICT_FALSE(c == 'v' && conv->flags != Flags::kBasic)) { + return nullptr; + } + + if (ABSL_PREDICT_FALSE(!tag.is_conv())) { + if (ABSL_PREDICT_FALSE(!tag.is_length())) return nullptr; + + // It is a length modifier. + using str_format_internal::LengthMod; + LengthMod length_mod = tag.as_length(); + ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); + if (c == 'h' && length_mod == LengthMod::h) { + conv->length_mod = LengthMod::hh; + ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); + } else if (c == 'l' && length_mod == LengthMod::l) { + conv->length_mod = LengthMod::ll; + ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); + } else { + conv->length_mod = length_mod; + } + tag = GetTagForChar(c); + + if (ABSL_PREDICT_FALSE(c == 'v')) return nullptr; + if (ABSL_PREDICT_FALSE(!tag.is_conv())) return nullptr; + } + + assert(CheckFastPathSetting(*conv)); + (void)(&CheckFastPathSetting); + + conv->conv = tag.as_conv(); + if (!is_positional) conv->arg_position = ++*next_arg; + return pos; +} + +// Consume conversion spec prefix (not including '%') of [p, end) if valid. +// Examples of valid specs would be e.g.: "s", "d", "-12.6f". +// If valid, it returns the first character following the conversion spec, +// and the spec part is broken down and returned in 'conv'. +// If invalid, returns nullptr. +constexpr const char* ConsumeUnboundConversion(const char* p, const char* end, + UnboundConversion* conv, + int* next_arg) { + if (*next_arg < 0) return ConsumeConversion(p, end, conv, next_arg); + return ConsumeConversion(p, end, conv, next_arg); +} + +} // namespace str_format_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_STRINGS_INTERNAL_STR_FORMAT_CONSTEXPR_PARSER_H_ diff --git a/absl/strings/internal/str_format/parser.cc b/absl/strings/internal/str_format/parser.cc index 13731ee2..5aaab698 100644 --- a/absl/strings/internal/str_format/parser.cc +++ b/absl/strings/internal/str_format/parser.cc @@ -31,211 +31,14 @@ namespace absl { ABSL_NAMESPACE_BEGIN namespace str_format_internal { -using CC = FormatConversionCharInternal; -using LM = LengthMod; +// Define the array for non-constexpr uses. +constexpr ConvTag ConvTagHolder::value[256]; -// Abbreviations to fit in the table below. -constexpr auto f_sign = Flags::kSignCol; -constexpr auto f_alt = Flags::kAlt; -constexpr auto f_pos = Flags::kShowPos; -constexpr auto f_left = Flags::kLeft; -constexpr auto f_zero = Flags::kZero; - -ABSL_CONST_INIT const ConvTag kTags[256] = { - {}, {}, {}, {}, {}, {}, {}, {}, // 00-07 - {}, {}, {}, {}, {}, {}, {}, {}, // 08-0f - {}, {}, {}, {}, {}, {}, {}, {}, // 10-17 - {}, {}, {}, {}, {}, {}, {}, {}, // 18-1f - f_sign, {}, {}, f_alt, {}, {}, {}, {}, // !"#$%&' - {}, {}, {}, f_pos, {}, f_left, {}, {}, // ()*+,-./ - f_zero, {}, {}, {}, {}, {}, {}, {}, // 01234567 - {}, {}, {}, {}, {}, {}, {}, {}, // 89:;<=>? - {}, CC::A, {}, {}, {}, CC::E, CC::F, CC::G, // @ABCDEFG - {}, {}, {}, {}, LM::L, {}, {}, {}, // HIJKLMNO - {}, {}, {}, {}, {}, {}, {}, {}, // PQRSTUVW - CC::X, {}, {}, {}, {}, {}, {}, {}, // XYZ[\]^_ - {}, CC::a, {}, CC::c, CC::d, CC::e, CC::f, CC::g, // `abcdefg - LM::h, CC::i, LM::j, {}, LM::l, {}, CC::n, CC::o, // hijklmno - CC::p, LM::q, {}, CC::s, LM::t, CC::u, CC::v, {}, // pqrstuvw - CC::x, {}, LM::z, {}, {}, {}, {}, {}, // xyz{|}! - {}, {}, {}, {}, {}, {}, {}, {}, // 80-87 - {}, {}, {}, {}, {}, {}, {}, {}, // 88-8f - {}, {}, {}, {}, {}, {}, {}, {}, // 90-97 - {}, {}, {}, {}, {}, {}, {}, {}, // 98-9f - {}, {}, {}, {}, {}, {}, {}, {}, // a0-a7 - {}, {}, {}, {}, {}, {}, {}, {}, // a8-af - {}, {}, {}, {}, {}, {}, {}, {}, // b0-b7 - {}, {}, {}, {}, {}, {}, {}, {}, // b8-bf - {}, {}, {}, {}, {}, {}, {}, {}, // c0-c7 - {}, {}, {}, {}, {}, {}, {}, {}, // c8-cf - {}, {}, {}, {}, {}, {}, {}, {}, // d0-d7 - {}, {}, {}, {}, {}, {}, {}, {}, // d8-df - {}, {}, {}, {}, {}, {}, {}, {}, // e0-e7 - {}, {}, {}, {}, {}, {}, {}, {}, // e8-ef - {}, {}, {}, {}, {}, {}, {}, {}, // f0-f7 - {}, {}, {}, {}, {}, {}, {}, {}, // f8-ff -}; - -namespace { - -bool CheckFastPathSetting(const UnboundConversion& conv) { - bool width_precision_needed = - conv.width.value() >= 0 || conv.precision.value() >= 0; - if (width_precision_needed && conv.flags == Flags::kBasic) { - fprintf(stderr, - "basic=%d left=%d show_pos=%d sign_col=%d alt=%d zero=%d " - "width=%d precision=%d\n", - conv.flags == Flags::kBasic ? 1 : 0, - FlagsContains(conv.flags, Flags::kLeft) ? 1 : 0, - FlagsContains(conv.flags, Flags::kShowPos) ? 1 : 0, - FlagsContains(conv.flags, Flags::kSignCol) ? 1 : 0, - FlagsContains(conv.flags, Flags::kAlt) ? 1 : 0, - FlagsContains(conv.flags, Flags::kZero) ? 1 : 0, conv.width.value(), - conv.precision.value()); - return false; - } - return true; -} - -template -const char *ConsumeConversion(const char *pos, const char *const end, - UnboundConversion *conv, int *next_arg) { - const char* const original_pos = pos; - char c; - // Read the next char into `c` and update `pos`. Returns false if there are - // no more chars to read. -#define ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR() \ - do { \ - if (ABSL_PREDICT_FALSE(pos == end)) return nullptr; \ - c = *pos++; \ - } while (0) - - const auto parse_digits = [&] { - int digits = c - '0'; - // We do not want to overflow `digits` so we consume at most digits10 - // digits. If there are more digits the parsing will fail later on when the - // digit doesn't match the expected characters. - int num_digits = std::numeric_limits::digits10; - for (;;) { - if (ABSL_PREDICT_FALSE(pos == end)) break; - c = *pos++; - if (!std::isdigit(c)) break; - --num_digits; - if (ABSL_PREDICT_FALSE(!num_digits)) break; - digits = 10 * digits + c - '0'; - } - return digits; - }; - - if (is_positional) { - ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); - if (ABSL_PREDICT_FALSE(c < '1' || c > '9')) return nullptr; - conv->arg_position = parse_digits(); - assert(conv->arg_position > 0); - if (ABSL_PREDICT_FALSE(c != '$')) return nullptr; - } - - ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); - - // We should start with the basic flag on. - assert(conv->flags == Flags::kBasic); - - // Any non alpha character makes this conversion not basic. - // This includes flags (-+ #0), width (1-9, *) or precision (.). - // All conversion characters and length modifiers are alpha characters. - if (c < 'A') { - while (c <= '0') { - auto tag = GetTagForChar(c); - if (tag.is_flags()) { - conv->flags = conv->flags | tag.as_flags(); - ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); - } else { - break; - } - } - - if (c <= '9') { - if (c >= '0') { - int maybe_width = parse_digits(); - if (!is_positional && c == '$') { - if (ABSL_PREDICT_FALSE(*next_arg != 0)) return nullptr; - // Positional conversion. - *next_arg = -1; - return ConsumeConversion(original_pos, end, conv, next_arg); - } - conv->flags = conv->flags | Flags::kNonBasic; - conv->width.set_value(maybe_width); - } else if (c == '*') { - conv->flags = conv->flags | Flags::kNonBasic; - ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); - if (is_positional) { - if (ABSL_PREDICT_FALSE(c < '1' || c > '9')) return nullptr; - conv->width.set_from_arg(parse_digits()); - if (ABSL_PREDICT_FALSE(c != '$')) return nullptr; - ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); - } else { - conv->width.set_from_arg(++*next_arg); - } - } - } - - if (c == '.') { - conv->flags = conv->flags | Flags::kNonBasic; - ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); - if (std::isdigit(c)) { - conv->precision.set_value(parse_digits()); - } else if (c == '*') { - ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); - if (is_positional) { - if (ABSL_PREDICT_FALSE(c < '1' || c > '9')) return nullptr; - conv->precision.set_from_arg(parse_digits()); - if (c != '$') return nullptr; - ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); - } else { - conv->precision.set_from_arg(++*next_arg); - } - } else { - conv->precision.set_value(0); - } - } - } - - auto tag = GetTagForChar(c); - - if (ABSL_PREDICT_FALSE(c == 'v' && (pos - original_pos) != 1)) return nullptr; - - if (ABSL_PREDICT_FALSE(!tag.is_conv())) { - if (ABSL_PREDICT_FALSE(!tag.is_length())) return nullptr; - - // It is a length modifier. - using str_format_internal::LengthMod; - LengthMod length_mod = tag.as_length(); - ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); - if (c == 'h' && length_mod == LengthMod::h) { - conv->length_mod = LengthMod::hh; - ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); - } else if (c == 'l' && length_mod == LengthMod::l) { - conv->length_mod = LengthMod::ll; - ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); - } else { - conv->length_mod = length_mod; - } - tag = GetTagForChar(c); - - if (ABSL_PREDICT_FALSE(c == 'v')) return nullptr; - if (ABSL_PREDICT_FALSE(!tag.is_conv())) return nullptr; - } - - assert(CheckFastPathSetting(*conv)); - (void)(&CheckFastPathSetting); - - conv->conv = tag.as_conv(); - if (!is_positional) conv->arg_position = ++*next_arg; - return pos; +ABSL_ATTRIBUTE_NOINLINE const char* ConsumeUnboundConversionNoInline( + const char* p, const char* end, UnboundConversion* conv, int* next_arg) { + return ConsumeUnboundConversion(p, end, conv, next_arg); } -} // namespace - std::string LengthModToString(LengthMod v) { switch (v) { case LengthMod::h: @@ -262,12 +65,6 @@ std::string LengthModToString(LengthMod v) { return ""; } -const char *ConsumeUnboundConversion(const char *p, const char *end, - UnboundConversion *conv, int *next_arg) { - if (*next_arg < 0) return ConsumeConversion(p, end, conv, next_arg); - return ConsumeConversion(p, end, conv, next_arg); -} - struct ParsedFormatBase::ParsedFormatConsumer { explicit ParsedFormatConsumer(ParsedFormatBase *parsedformat) : parsed(parsedformat), data_pos(parsedformat->data_.get()) {} diff --git a/absl/strings/internal/str_format/parser.h b/absl/strings/internal/str_format/parser.h index a81bac83..35b6d49c 100644 --- a/absl/strings/internal/str_format/parser.h +++ b/absl/strings/internal/str_format/parser.h @@ -29,111 +29,18 @@ #include #include "absl/strings/internal/str_format/checker.h" +#include "absl/strings/internal/str_format/constexpr_parser.h" #include "absl/strings/internal/str_format/extension.h" namespace absl { ABSL_NAMESPACE_BEGIN namespace str_format_internal { -enum class LengthMod : std::uint8_t { h, hh, l, ll, L, j, z, t, q, none }; - std::string LengthModToString(LengthMod v); -// The analyzed properties of a single specified conversion. -struct UnboundConversion { - UnboundConversion() {} - - class InputValue { - public: - void set_value(int value) { - assert(value >= 0); - value_ = value; - } - int value() const { return value_; } - - // Marks the value as "from arg". aka the '*' format. - // Requires `value >= 1`. - // When set, is_from_arg() return true and get_from_arg() returns the - // original value. - // `value()`'s return value is unspecfied in this state. - void set_from_arg(int value) { - assert(value > 0); - value_ = -value - 1; - } - bool is_from_arg() const { return value_ < -1; } - int get_from_arg() const { - assert(is_from_arg()); - return -value_ - 1; - } - - private: - int value_ = -1; - }; - - // No need to initialize. It will always be set in the parser. - int arg_position; - - InputValue width; - InputValue precision; - - Flags flags = Flags::kBasic; - LengthMod length_mod = LengthMod::none; - FormatConversionChar conv = FormatConversionCharInternal::kNone; -}; - -// Consume conversion spec prefix (not including '%') of [p, end) if valid. -// Examples of valid specs would be e.g.: "s", "d", "-12.6f". -// If valid, it returns the first character following the conversion spec, -// and the spec part is broken down and returned in 'conv'. -// If invalid, returns nullptr. -const char* ConsumeUnboundConversion(const char* p, const char* end, - UnboundConversion* conv, int* next_arg); - -// Helper tag class for the table below. -// It allows fast `char -> ConversionChar/LengthMod/Flags` checking and -// conversions. -class ConvTag { - public: - constexpr ConvTag(FormatConversionChar conversion_char) // NOLINT - : tag_(static_cast(conversion_char)) {} - constexpr ConvTag(LengthMod length_mod) // NOLINT - : tag_(0x80 | static_cast(length_mod)) {} - constexpr ConvTag(Flags flags) // NOLINT - : tag_(0xc0 | static_cast(flags)) {} - constexpr ConvTag() : tag_(0xFF) {} - - bool is_conv() const { return (tag_ & 0x80) == 0; } - bool is_length() const { return (tag_ & 0xC0) == 0x80; } - bool is_flags() const { return (tag_ & 0xE0) == 0xC0; } - - FormatConversionChar as_conv() const { - assert(is_conv()); - assert(!is_length()); - assert(!is_flags()); - return static_cast(tag_); - } - LengthMod as_length() const { - assert(!is_conv()); - assert(is_length()); - assert(!is_flags()); - return static_cast(tag_ & 0x3F); - } - Flags as_flags() const { - assert(!is_conv()); - assert(!is_length()); - assert(is_flags()); - return static_cast(tag_ & 0x1F); - } - - private: - uint8_t tag_; -}; - -extern const ConvTag kTags[256]; -// Keep a single table for all the conversion chars and length modifiers. -inline ConvTag GetTagForChar(char c) { - return kTags[static_cast(c)]; -} +const char* ConsumeUnboundConversionNoInline(const char* p, const char* end, + UnboundConversion* conv, + int* next_arg); // Parse the format string provided in 'src' and pass the identified items into // 'consumer'. @@ -187,7 +94,7 @@ bool ParseFormatString(string_view src, Consumer consumer) { } } else if (percent[1] != '%') { UnboundConversion conv; - p = ConsumeUnboundConversion(percent + 1, end, &conv, &next_arg); + p = ConsumeUnboundConversionNoInline(percent + 1, end, &conv, &next_arg); if (ABSL_PREDICT_FALSE(p == nullptr)) return false; if (ABSL_PREDICT_FALSE(!consumer.ConvertOne( conv, string_view(percent + 1, diff --git a/absl/strings/internal/str_format/parser_test.cc b/absl/strings/internal/str_format/parser_test.cc index c3e825fe..021f6a87 100644 --- a/absl/strings/internal/str_format/parser_test.cc +++ b/absl/strings/internal/str_format/parser_test.cc @@ -117,6 +117,7 @@ TEST_F(ConsumeUnboundConversionTest, ConsumeSpecification) { {__LINE__, "dzz", "d", "zz"}, // length mod as suffix {__LINE__, "3v", "", "3v"}, // 'v' cannot have modifiers {__LINE__, "hv", "", "hv"}, // 'v' cannot have modifiers + {__LINE__, "1$v", "1$v", ""}, // 'v' can have use posix syntax {__LINE__, "1$*2$d", "1$*2$d", "" }, // arg indexing and * allowed. {__LINE__, "0-14.3hhd", "0-14.3hhd", ""}, // precision, width {__LINE__, " 0-+#14.3hhd", " 0-+#14.3hhd", ""}, // flags diff --git a/absl/strings/str_format_test.cc b/absl/strings/str_format_test.cc index 2aa22b0d..36def1e0 100644 --- a/absl/strings/str_format_test.cc +++ b/absl/strings/str_format_test.cc @@ -143,13 +143,20 @@ TEST_F(FormatEntryPointTest, AppendFormatFailWithV) { } TEST_F(FormatEntryPointTest, ManyArgs) { - EXPECT_EQ("24", StrFormat("%24$d", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, - 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24)); - EXPECT_EQ("60", StrFormat("%60$d", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, - 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, - 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, - 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, - 53, 54, 55, 56, 57, 58, 59, 60)); + EXPECT_EQ( + "60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 " + "36 35 34 33 32 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 " + "12 11 10 9 8 7 6 5 4 3 2 1", + StrFormat("%60$d %59$d %58$d %57$d %56$d %55$d %54$d %53$d %52$d %51$d " + "%50$d %49$d %48$d %47$d %46$d %45$d %44$d %43$d %42$d %41$d " + "%40$d %39$d %38$d %37$d %36$d %35$d %34$d %33$d %32$d %31$d " + "%30$d %29$d %28$d %27$d %26$d %25$d %24$d %23$d %22$d %21$d " + "%20$d %19$d %18$d %17$d %16$d %15$d %14$d %13$d %12$d %11$d " + "%10$d %9$d %8$d %7$d %6$d %5$d %4$d %3$d %2$d %1$d", + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, + 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, + 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, + 51, 52, 53, 54, 55, 56, 57, 58, 59, 60)); } TEST_F(FormatEntryPointTest, Preparsed) { -- cgit v1.2.3 From 9bff2a9302a8dbf91712fc215eb2e2cf8ec234e7 Mon Sep 17 00:00:00 2001 From: Phoebe Liang Date: Wed, 7 Dec 2022 12:49:08 -0800 Subject: Fixes issue where AbslStringify() breaks formatting with %d for enums PiperOrigin-RevId: 493682437 Change-Id: I30f2ac36b998b86c24fe7513cd952b860560a66e --- absl/strings/internal/str_format/arg.cc | 92 ++++++++++++++++++++++----------- absl/strings/internal/str_format/arg.h | 88 +++++++++++++++++++++++++------ absl/strings/str_format_test.cc | 37 ++++++++++++- 3 files changed, 170 insertions(+), 47 deletions(-) (limited to 'absl/strings/str_format_test.cc') diff --git a/absl/strings/internal/str_format/arg.cc b/absl/strings/internal/str_format/arg.cc index 967fe9ca..018dd052 100644 --- a/absl/strings/internal/str_format/arg.cc +++ b/absl/strings/internal/str_format/arg.cc @@ -296,6 +296,37 @@ constexpr auto ConvertV(T) { return FormatConversionCharInternal::u; } +template +bool ConvertFloatArg(T v, FormatConversionSpecImpl conv, FormatSinkImpl *sink) { + if (conv.conversion_char() == FormatConversionCharInternal::v) { + conv.set_conversion_char(FormatConversionCharInternal::g); + } + + return FormatConversionCharIsFloat(conv.conversion_char()) && + ConvertFloatImpl(v, conv, sink); +} + +inline bool ConvertStringArg(string_view v, const FormatConversionSpecImpl conv, + FormatSinkImpl *sink) { + if (conv.is_basic()) { + sink->Append(v); + return true; + } + return sink->PutPaddedString(v, conv.width(), conv.precision(), + conv.has_left_flag()); +} + +} // namespace + +bool ConvertBoolArg(bool v, FormatSinkImpl *sink) { + if (v) { + sink->Append("true"); + } else { + sink->Append("false"); + } + return true; +} + template bool ConvertIntArg(T v, FormatConversionSpecImpl conv, FormatSinkImpl *sink) { using U = typename MakeUnsigned::type; @@ -354,36 +385,37 @@ bool ConvertIntArg(T v, FormatConversionSpecImpl conv, FormatSinkImpl *sink) { return ConvertIntImplInnerSlow(as_digits, conv, sink); } -template -bool ConvertFloatArg(T v, FormatConversionSpecImpl conv, FormatSinkImpl *sink) { - if (conv.conversion_char() == FormatConversionCharInternal::v) { - conv.set_conversion_char(FormatConversionCharInternal::g); - } - - return FormatConversionCharIsFloat(conv.conversion_char()) && - ConvertFloatImpl(v, conv, sink); -} - -inline bool ConvertStringArg(string_view v, const FormatConversionSpecImpl conv, - FormatSinkImpl *sink) { - if (conv.is_basic()) { - sink->Append(v); - return true; - } - return sink->PutPaddedString(v, conv.width(), conv.precision(), - conv.has_left_flag()); -} - -} // namespace - -bool ConvertBoolArg(bool v, FormatSinkImpl *sink) { - if (v) { - sink->Append("true"); - } else { - sink->Append("false"); - } - return true; -} +template bool ConvertIntArg(char v, FormatConversionSpecImpl conv, + FormatSinkImpl *sink); +template bool ConvertIntArg(signed char v, + FormatConversionSpecImpl conv, + FormatSinkImpl *sink); +template bool ConvertIntArg(unsigned char v, + FormatConversionSpecImpl conv, + FormatSinkImpl *sink); +template bool ConvertIntArg(short v, // NOLINT + FormatConversionSpecImpl conv, + FormatSinkImpl *sink); +template bool ConvertIntArg(unsigned short v, // NOLINT + FormatConversionSpecImpl conv, + FormatSinkImpl *sink); +template bool ConvertIntArg(int v, FormatConversionSpecImpl conv, + FormatSinkImpl *sink); +template bool ConvertIntArg(unsigned int v, + FormatConversionSpecImpl conv, + FormatSinkImpl *sink); +template bool ConvertIntArg(long v, // NOLINT + FormatConversionSpecImpl conv, + FormatSinkImpl *sink); +template bool ConvertIntArg(unsigned long v, // NOLINT + FormatConversionSpecImpl conv, + FormatSinkImpl *sink); +template bool ConvertIntArg(long long v, // NOLINT + FormatConversionSpecImpl conv, + FormatSinkImpl *sink); +template bool ConvertIntArg(unsigned long long v, // NOLINT + FormatConversionSpecImpl conv, + FormatSinkImpl *sink); // ==================== Strings ==================== StringConvertResult FormatConvertImpl(const std::string &v, diff --git a/absl/strings/internal/str_format/arg.h b/absl/strings/internal/str_format/arg.h index bc4cde96..e4b16628 100644 --- a/absl/strings/internal/str_format/arg.h +++ b/absl/strings/internal/str_format/arg.h @@ -53,6 +53,19 @@ struct ArgConvertResult { bool value; }; +using IntegralConvertResult = ArgConvertResult; +using FloatingConvertResult = ArgConvertResult; +using CharConvertResult = ArgConvertResult; + template struct HasUserDefinedConvert : std::false_type {}; @@ -69,6 +82,44 @@ struct HasUserDefinedConvert +bool ConvertIntArg(T v, FormatConversionSpecImpl conv, FormatSinkImpl* sink); + +// Forward declarations of internal `ConvertIntArg` function template +// instantiations are here to avoid including the template body in the headers +// and instantiating it in large numbers of translation units. Explicit +// instantiations can be found in "absl/strings/internal/str_format/arg.cc" +extern template bool ConvertIntArg(char v, FormatConversionSpecImpl conv, + FormatSinkImpl* sink); +extern template bool ConvertIntArg(signed char v, + FormatConversionSpecImpl conv, + FormatSinkImpl* sink); +extern template bool ConvertIntArg(unsigned char v, + FormatConversionSpecImpl conv, + FormatSinkImpl* sink); +extern template bool ConvertIntArg(short v, // NOLINT + FormatConversionSpecImpl conv, + FormatSinkImpl* sink); +extern template bool ConvertIntArg( // NOLINT + unsigned short v, FormatConversionSpecImpl conv, // NOLINT + FormatSinkImpl* sink); +extern template bool ConvertIntArg(int v, FormatConversionSpecImpl conv, + FormatSinkImpl* sink); +extern template bool ConvertIntArg(unsigned int v, + FormatConversionSpecImpl conv, + FormatSinkImpl* sink); +extern template bool ConvertIntArg( // NOLINT + long v, FormatConversionSpecImpl conv, FormatSinkImpl* sink); // NOLINT +extern template bool ConvertIntArg(unsigned long v, // NOLINT + FormatConversionSpecImpl conv, + FormatSinkImpl* sink); +extern template bool ConvertIntArg(long long v, // NOLINT + FormatConversionSpecImpl conv, + FormatSinkImpl* sink); +extern template bool ConvertIntArg( // NOLINT + unsigned long long v, FormatConversionSpecImpl conv, // NOLINT + FormatSinkImpl* sink); + template auto FormatConvertImpl(const T& v, FormatConversionSpecImpl conv, FormatSinkImpl* sink) @@ -84,11 +135,31 @@ auto FormatConvertImpl(const T& v, FormatConversionSpecImpl conv, return AbslFormatConvert(v, fcs, &fs); } +template +auto FormatConvertImpl(const T& v, FormatConversionSpecImpl conv, + FormatSinkImpl* sink) + -> std::enable_if_t::value && + std::is_void(), v))>::value, + IntegralConvertResult> { + if (conv.conversion_char() == FormatConversionCharInternal::v) { + using FormatSinkT = + absl::enable_if_t; + auto fs = sink->Wrap(); + AbslStringify(fs, v); + return {true}; + } else { + return {ConvertIntArg( + static_cast::type>(v), conv, sink)}; + } +} + template auto FormatConvertImpl(const T& v, FormatConversionSpecImpl, FormatSinkImpl* sink) - -> std::enable_if_t(), v))>::value, + -> std::enable_if_t::value && + std::is_void(), v))>::value, ArgConvertResult> { using FormatSinkT = absl::enable_if_t; @@ -194,19 +265,6 @@ StringConvertResult FormatConvertImpl(const AbslCord& value, return {true}; } -using IntegralConvertResult = ArgConvertResult; -using FloatingConvertResult = ArgConvertResult; -using CharConvertResult = ArgConvertResult; - bool ConvertBoolArg(bool v, FormatSinkImpl* sink); // Floats. diff --git a/absl/strings/str_format_test.cc b/absl/strings/str_format_test.cc index 36def1e0..5198fb33 100644 --- a/absl/strings/str_format_test.cc +++ b/absl/strings/str_format_test.cc @@ -1142,18 +1142,51 @@ TEST_F(FormatExtensionTest, AbslStringifyExampleUsingFormat) { EXPECT_EQ(absl::StrFormat("a %v z", p), "a (10, 20) z"); } -enum class EnumWithStringify { Many = 0, Choices = 1 }; +enum class EnumClassWithStringify { Many = 0, Choices = 1 }; + +template +void AbslStringify(Sink& sink, EnumClassWithStringify e) { + absl::Format(&sink, "%s", + e == EnumClassWithStringify::Many ? "Many" : "Choices"); +} + +enum EnumWithStringify { Many, Choices }; template void AbslStringify(Sink& sink, EnumWithStringify e) { absl::Format(&sink, "%s", e == EnumWithStringify::Many ? "Many" : "Choices"); } -TEST_F(FormatExtensionTest, AbslStringifyWithEnum) { +TEST_F(FormatExtensionTest, AbslStringifyWithEnumWithV) { + const auto e_class = EnumClassWithStringify::Choices; + EXPECT_EQ(absl::StrFormat("My choice is %v", e_class), + "My choice is Choices"); + const auto e = EnumWithStringify::Choices; EXPECT_EQ(absl::StrFormat("My choice is %v", e), "My choice is Choices"); } +TEST_F(FormatExtensionTest, AbslStringifyEnumWithD) { + const auto e_class = EnumClassWithStringify::Many; + EXPECT_EQ(absl::StrFormat("My choice is %d", e_class), "My choice is 0"); + + const auto e = EnumWithStringify::Choices; + EXPECT_EQ(absl::StrFormat("My choice is %d", e), "My choice is 1"); +} + +enum class EnumWithLargerValue { x = 32 }; + +template +void AbslStringify(Sink& sink, EnumWithLargerValue e) { + absl::Format(&sink, "%s", "Many"); +} + +TEST_F(FormatExtensionTest, AbslStringifyEnumOtherSpecifiers) { + const auto e = EnumWithLargerValue::x; + EXPECT_EQ(absl::StrFormat("My choice is %g", e), "My choice is 32"); + EXPECT_EQ(absl::StrFormat("My choice is %x", e), "My choice is 20"); +} + } // namespace // Some codegen thunks that we can use to easily dump the generated assembly for -- cgit v1.2.3