Export of internal Abseil changes.
-- 70f43a482d7d4ae4a255f17ca02b0106653dd600 by Shaindel Schwartz <shaindel@google.com>: Internal change PiperOrigin-RevId: 201571193 -- 93e6e9c2e683158be49d9dd1f5cb1a91d0c0f556 by Abseil Team <absl-team@google.com>: Internal change. PiperOrigin-RevId: 201567108 -- fbd8ee94fbe9f2448e5adf5e88706f9c8216048f by Juemin Yang <jueminyang@google.com>: str_format release PiperOrigin-RevId: 201565129 -- 387faa301555a8a888c4429df52734aa806dca46 by Abseil Team <absl-team@google.com>: Adds a defaulted allocator parameter to the size_type constructor of InlinedVector PiperOrigin-RevId: 201558711 -- 39b15ea2c68d7129d70cbde7e71af900032595ec by Matt Calabrese <calabrese@google.com>: Update the variant implementation to eliminate unnecessary checking on alternative access when the index is known or required to be correct. PiperOrigin-RevId: 201529535 -- adab77f1f7bb363aa534297f22aae2b0f08889ea by Abseil Team <absl-team@google.com>: Import of CCTZ from GitHub. PiperOrigin-RevId: 201458388 -- a701dc0ba62e3cadf0de14203415b91df4ee8151 by Greg Falcon <gfalcon@google.com>: Internal cleanup PiperOrigin-RevId: 201394836 -- 8a7191410b8f440fdfa27f722ff05e451502ab61 by Abseil Team <absl-team@google.com>: Import of CCTZ from GitHub. PiperOrigin-RevId: 201369269 GitOrigin-RevId: 70f43a482d7d4ae4a255f17ca02b0106653dd600 Change-Id: I8ab073b30b4e27405a3b6da2c826bb4f3f0b9af6
This commit is contained in:
		
							parent
							
								
									d89dba27e3
								
							
						
					
					
						commit
						4491d606df
					
				
					 46 changed files with 6559 additions and 354 deletions
				
			
		|  | @ -89,7 +89,9 @@ class InlinedVector { | |||
|       : allocator_and_tag_(alloc) {} | ||||
| 
 | ||||
|   // Create a vector with n copies of value_type().
 | ||||
|   explicit InlinedVector(size_type n) : allocator_and_tag_(allocator_type()) { | ||||
|   explicit InlinedVector(size_type n, | ||||
|                          const allocator_type& alloc = allocator_type()) | ||||
|       : allocator_and_tag_(alloc) { | ||||
|     InitAssign(n); | ||||
|   } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1763,4 +1763,30 @@ TEST(AllocatorSupportTest, ScopedAllocatorWorks) { | |||
|   EXPECT_EQ(allocated, 0); | ||||
| } | ||||
| 
 | ||||
| TEST(AllocatorSupportTest, SizeAllocConstructor) { | ||||
|   constexpr int inlined_size = 4; | ||||
|   using Alloc = CountingAllocator<int>; | ||||
|   using AllocVec = absl::InlinedVector<int, inlined_size, Alloc>; | ||||
| 
 | ||||
|   { | ||||
|     auto len = inlined_size / 2; | ||||
|     int64_t allocated = 0; | ||||
|     auto v = AllocVec(len, Alloc(&allocated)); | ||||
| 
 | ||||
|     // Inline storage used; allocator should not be invoked
 | ||||
|     EXPECT_THAT(allocated, 0); | ||||
|     EXPECT_THAT(v, AllOf(SizeIs(len), Each(0))); | ||||
|   } | ||||
| 
 | ||||
|   { | ||||
|     auto len = inlined_size * 2; | ||||
|     int64_t allocated = 0; | ||||
|     auto v = AllocVec(len, Alloc(&allocated)); | ||||
| 
 | ||||
|     // Out of line storage used; allocation of 8 elements expected
 | ||||
|     EXPECT_THAT(allocated, len * sizeof(int)); | ||||
|     EXPECT_THAT(v, AllOf(SizeIs(len), Each(0))); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| }  // anonymous namespace
 | ||||
|  |  | |||
|  | @ -492,3 +492,142 @@ cc_test( | |||
|         "@com_github_google_benchmark//:benchmark_main", | ||||
|     ], | ||||
| ) | ||||
| 
 | ||||
| cc_library( | ||||
|     name = "str_format", | ||||
|     hdrs = [ | ||||
|         "str_format.h", | ||||
|     ], | ||||
|     copts = ABSL_DEFAULT_COPTS, | ||||
|     deps = [ | ||||
|         ":str_format_internal", | ||||
|     ], | ||||
| ) | ||||
| 
 | ||||
| cc_library( | ||||
|     name = "str_format_internal", | ||||
|     srcs = [ | ||||
|         "internal/str_format/arg.cc", | ||||
|         "internal/str_format/bind.cc", | ||||
|         "internal/str_format/extension.cc", | ||||
|         "internal/str_format/float_conversion.cc", | ||||
|         "internal/str_format/output.cc", | ||||
|         "internal/str_format/parser.cc", | ||||
|     ], | ||||
|     hdrs = [ | ||||
|         "internal/str_format/arg.h", | ||||
|         "internal/str_format/bind.h", | ||||
|         "internal/str_format/checker.h", | ||||
|         "internal/str_format/extension.h", | ||||
|         "internal/str_format/float_conversion.h", | ||||
|         "internal/str_format/output.h", | ||||
|         "internal/str_format/parser.h", | ||||
|     ], | ||||
|     copts = ABSL_DEFAULT_COPTS, | ||||
|     visibility = ["//visibility:private"], | ||||
|     deps = [ | ||||
|         ":strings", | ||||
|         "//absl/base:core_headers", | ||||
|         "//absl/container:inlined_vector", | ||||
|         "//absl/meta:type_traits", | ||||
|         "//absl/numeric:int128", | ||||
|         "//absl/types:span", | ||||
|     ], | ||||
| ) | ||||
| 
 | ||||
| cc_test( | ||||
|     name = "str_format_test", | ||||
|     srcs = ["str_format_test.cc"], | ||||
|     copts = ABSL_TEST_COPTS, | ||||
|     visibility = ["//visibility:private"], | ||||
|     deps = [ | ||||
|         ":str_format", | ||||
|         ":strings", | ||||
|         "//absl/base:core_headers", | ||||
|         "@com_google_googletest//:gtest_main", | ||||
|     ], | ||||
| ) | ||||
| 
 | ||||
| cc_test( | ||||
|     name = "str_format_extension_test", | ||||
|     srcs = [ | ||||
|         "internal/str_format/extension_test.cc", | ||||
|     ], | ||||
|     copts = ABSL_TEST_COPTS, | ||||
|     visibility = ["//visibility:private"], | ||||
|     deps = [ | ||||
|         ":str_format", | ||||
|         ":str_format_internal", | ||||
|         "@com_google_googletest//:gtest_main", | ||||
|     ], | ||||
| ) | ||||
| 
 | ||||
| cc_test( | ||||
|     name = "str_format_arg_test", | ||||
|     srcs = ["internal/str_format/arg_test.cc"], | ||||
|     copts = ABSL_TEST_COPTS, | ||||
|     visibility = ["//visibility:private"], | ||||
|     deps = [ | ||||
|         ":str_format", | ||||
|         ":str_format_internal", | ||||
|         "@com_google_googletest//:gtest_main", | ||||
|     ], | ||||
| ) | ||||
| 
 | ||||
| cc_test( | ||||
|     name = "str_format_bind_test", | ||||
|     srcs = ["internal/str_format/bind_test.cc"], | ||||
|     copts = ABSL_TEST_COPTS, | ||||
|     visibility = ["//visibility:private"], | ||||
|     deps = [ | ||||
|         ":str_format_internal", | ||||
|         "@com_google_googletest//:gtest_main", | ||||
|     ], | ||||
| ) | ||||
| 
 | ||||
| cc_test( | ||||
|     name = "str_format_checker_test", | ||||
|     srcs = ["internal/str_format/checker_test.cc"], | ||||
|     copts = ABSL_TEST_COPTS, | ||||
|     visibility = ["//visibility:private"], | ||||
|     deps = [ | ||||
|         ":str_format", | ||||
|         "@com_google_googletest//:gtest_main", | ||||
|     ], | ||||
| ) | ||||
| 
 | ||||
| cc_test( | ||||
|     name = "str_format_convert_test", | ||||
|     size = "small", | ||||
|     srcs = ["internal/str_format/convert_test.cc"], | ||||
|     copts = ABSL_TEST_COPTS, | ||||
|     visibility = ["//visibility:private"], | ||||
|     deps = [ | ||||
|         ":str_format_internal", | ||||
|         "//absl/numeric:int128", | ||||
|         "@com_google_googletest//:gtest_main", | ||||
|     ], | ||||
| ) | ||||
| 
 | ||||
| cc_test( | ||||
|     name = "str_format_output_test", | ||||
|     srcs = ["internal/str_format/output_test.cc"], | ||||
|     copts = ABSL_TEST_COPTS, | ||||
|     visibility = ["//visibility:private"], | ||||
|     deps = [ | ||||
|         ":str_format_internal", | ||||
|         "@com_google_googletest//:gtest_main", | ||||
|     ], | ||||
| ) | ||||
| 
 | ||||
| cc_test( | ||||
|     name = "str_format_parser_test", | ||||
|     srcs = ["internal/str_format/parser_test.cc"], | ||||
|     copts = ABSL_TEST_COPTS, | ||||
|     visibility = ["//visibility:private"], | ||||
|     deps = [ | ||||
|         ":str_format_internal", | ||||
|         "//absl/base:core_headers", | ||||
|         "@com_google_googletest//:gtest_main", | ||||
|     ], | ||||
| ) | ||||
|  |  | |||
|  | @ -81,6 +81,58 @@ absl_library( | |||
|     strings | ||||
| ) | ||||
| 
 | ||||
| # add str_format library | ||||
| absl_library( | ||||
|   TARGET | ||||
|     absl_str_format | ||||
|   SOURCES | ||||
|     "str_format.h" | ||||
|   PUBLIC_LIBRARIES | ||||
|     str_format_internal | ||||
|   EXPORT_NAME | ||||
|     str_format | ||||
| ) | ||||
| 
 | ||||
| # str_format_internal | ||||
|  absl_library( | ||||
|   TARGET | ||||
|     str_format_internal | ||||
|   SOURCES | ||||
|     "internal/str_format/arg.cc" | ||||
|     "internal/str_format/bind.cc" | ||||
|     "internal/str_format/extension.cc" | ||||
|     "internal/str_format/float_conversion.cc" | ||||
|     "internal/str_format/output.cc" | ||||
|     "internal/str_format/parser.cc" | ||||
|     "internal/str_format/arg.h" | ||||
|     "internal/str_format/bind.h" | ||||
|     "internal/str_format/checker.h" | ||||
|     "internal/str_format/extension.h" | ||||
|     "internal/str_format/float_conversion.h" | ||||
|     "internal/str_format/output.h" | ||||
|     "internal/str_format/parser.h" | ||||
|   PUBLIC_LIBRARIES | ||||
|     str_format_extension_internal | ||||
|     absl::strings | ||||
|     absl::base | ||||
|     absl::numeric | ||||
|     absl::container | ||||
|     absl::span | ||||
| ) | ||||
| 
 | ||||
| # str_format_extension_internal | ||||
| absl_library( | ||||
|   TARGET | ||||
|   str_format_extension_internal | ||||
|   SOURCES | ||||
|     "internal/str_format/extension.cc" | ||||
|     "internal/str_format/extension.h" | ||||
|     "internal/str_format/output.cc" | ||||
|     "internal/str_format/output.h" | ||||
|   PUBLIC_LIBRARIES | ||||
|     absl::base | ||||
|     absl::strings | ||||
| ) | ||||
| 
 | ||||
| # | ||||
| ## TESTS | ||||
|  | @ -347,3 +399,68 @@ absl_test( | |||
|   PUBLIC_LIBRARIES | ||||
|     ${CHARCONV_BIGINT_TEST_PUBLIC_LIBRARIES} | ||||
| ) | ||||
| # test str_format_test | ||||
| absl_test( | ||||
|   TARGET | ||||
|     str_format_test | ||||
|   SOURCES | ||||
|     "str_format_test.cc" | ||||
|   PUBLIC_LIBRARIES | ||||
|     absl::base | ||||
|     absl::str_format | ||||
|     absl::strings | ||||
| ) | ||||
| 
 | ||||
| # test str_format_bind_test | ||||
| absl_test( | ||||
|   TARGET | ||||
|     str_format_bind_test | ||||
|   SOURCES | ||||
|     "internal/str_format/bind_test.cc" | ||||
|   PUBLIC_LIBRARIES | ||||
|     str_format_internal | ||||
| ) | ||||
| 
 | ||||
| # test str_format_checker_test | ||||
| absl_test( | ||||
|   TARGET | ||||
|     str_format_checker_test | ||||
|   SOURCES | ||||
|     "internal/str_format/checker_test.cc" | ||||
|   PUBLIC_LIBRARIES | ||||
|     absl::str_format | ||||
| ) | ||||
| 
 | ||||
| # test str_format_convert_test | ||||
| absl_test( | ||||
|   TARGET | ||||
|     str_format_convert_test | ||||
|   SOURCES | ||||
|     "internal/str_format/convert_test.cc" | ||||
|   PUBLIC_LIBRARIES | ||||
|     str_format_internal | ||||
|     absl::numeric | ||||
| ) | ||||
| 
 | ||||
| # test str_format_output_test | ||||
| absl_test( | ||||
|   TARGET | ||||
|     str_format_output_test | ||||
|   SOURCES | ||||
|     "internal/str_format/output_test.cc" | ||||
|   PUBLIC_LIBRARIES | ||||
|     str_format_extension_internal | ||||
| ) | ||||
| 
 | ||||
| # test str_format_parser_test | ||||
| absl_test( | ||||
|   TARGET | ||||
|     str_format_parser_test | ||||
|   SOURCES | ||||
|     "internal/str_format/parser_test.cc" | ||||
|   PUBLIC_LIBRARIES | ||||
|     str_format_internal | ||||
|     absl::base | ||||
| ) | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										399
									
								
								absl/strings/internal/str_format/arg.cc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										399
									
								
								absl/strings/internal/str_format/arg.cc
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,399 @@ | |||
| //
 | ||||
| // POSIX spec:
 | ||||
| //   http://pubs.opengroup.org/onlinepubs/009695399/functions/fprintf.html
 | ||||
| //
 | ||||
| #include "absl/strings/internal/str_format/arg.h" | ||||
| 
 | ||||
| #include <cassert> | ||||
| #include <cerrno> | ||||
| #include <cstdlib> | ||||
| #include <string> | ||||
| #include <type_traits> | ||||
| 
 | ||||
| #include "absl/base/port.h" | ||||
| #include "absl/strings/internal/str_format/float_conversion.h" | ||||
| 
 | ||||
| namespace absl { | ||||
| namespace str_format_internal { | ||||
| namespace { | ||||
| 
 | ||||
| const char kDigit[2][32] = { "0123456789abcdef", "0123456789ABCDEF" }; | ||||
| 
 | ||||
| // Reduce *capacity by s.size(), clipped to a 0 minimum.
 | ||||
| void ReducePadding(string_view s, size_t *capacity) { | ||||
|   *capacity = Excess(s.size(), *capacity); | ||||
| } | ||||
| 
 | ||||
| // Reduce *capacity by n, clipped to a 0 minimum.
 | ||||
| void ReducePadding(size_t n, size_t *capacity) { | ||||
|   *capacity = Excess(n, *capacity); | ||||
| } | ||||
| 
 | ||||
| template <typename T> | ||||
| struct MakeUnsigned : std::make_unsigned<T> {}; | ||||
| template <> | ||||
| struct MakeUnsigned<absl::uint128> { | ||||
|   using type = absl::uint128; | ||||
| }; | ||||
| 
 | ||||
| template <typename T> | ||||
| struct IsSigned : std::is_signed<T> {}; | ||||
| template <> | ||||
| struct IsSigned<absl::uint128> : std::false_type {}; | ||||
| 
 | ||||
| class ConvertedIntInfo { | ||||
|  public: | ||||
|   template <typename T> | ||||
|   ConvertedIntInfo(T v, ConversionChar conv) { | ||||
|     using Unsigned = typename MakeUnsigned<T>::type; | ||||
|     auto u = static_cast<Unsigned>(v); | ||||
|     if (IsNeg(v)) { | ||||
|       is_neg_ = true; | ||||
|       u = Unsigned{} - u; | ||||
|     } else { | ||||
|       is_neg_ = false; | ||||
|     } | ||||
|     UnsignedToStringRight(u, conv); | ||||
|   } | ||||
| 
 | ||||
|   string_view digits() const { | ||||
|     return {end() - size_, static_cast<size_t>(size_)}; | ||||
|   } | ||||
|   bool is_neg() const { return is_neg_; } | ||||
| 
 | ||||
|  private: | ||||
|   template <typename T, bool IsSigned> | ||||
|   struct IsNegImpl { | ||||
|     static bool Eval(T v) { return v < 0; } | ||||
|   }; | ||||
|   template <typename T> | ||||
|   struct IsNegImpl<T, false> { | ||||
|     static bool Eval(T) { | ||||
|       return false; | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   template <typename T> | ||||
|   bool IsNeg(T v) { | ||||
|     return IsNegImpl<T, IsSigned<T>::value>::Eval(v); | ||||
|   } | ||||
| 
 | ||||
|   template <typename T> | ||||
|   void UnsignedToStringRight(T u, ConversionChar conv) { | ||||
|     char *p = end(); | ||||
|     switch (conv.radix()) { | ||||
|       default: | ||||
|       case 10: | ||||
|         for (; u; u /= 10) | ||||
|           *--p = static_cast<char>('0' + static_cast<size_t>(u % 10)); | ||||
|         break; | ||||
|       case 8: | ||||
|         for (; u; u /= 8) | ||||
|           *--p = static_cast<char>('0' + static_cast<size_t>(u % 8)); | ||||
|         break; | ||||
|       case 16: { | ||||
|         const char *digits = kDigit[conv.upper() ? 1 : 0]; | ||||
|         for (; u; u /= 16) *--p = digits[static_cast<size_t>(u % 16)]; | ||||
|         break; | ||||
|       } | ||||
|     } | ||||
|     size_ = static_cast<int>(end() - p); | ||||
|   } | ||||
| 
 | ||||
|   const char *end() const { return storage_ + sizeof(storage_); } | ||||
|   char *end() { return storage_ + sizeof(storage_); } | ||||
| 
 | ||||
|   bool is_neg_; | ||||
|   int size_; | ||||
|   // Max size: 128 bit value as octal -> 43 digits
 | ||||
|   char storage_[128 / 3 + 1]; | ||||
| }; | ||||
| 
 | ||||
| // Note: 'o' conversions do not have a base indicator, it's just that
 | ||||
| // the '#' flag is specified to modify the precision for 'o' conversions.
 | ||||
| string_view BaseIndicator(const ConvertedIntInfo &info, | ||||
|                           const ConversionSpec &conv) { | ||||
|   bool alt = conv.flags().alt; | ||||
|   int radix = conv.conv().radix(); | ||||
|   if (conv.conv().id() == ConversionChar::p) | ||||
|     alt = true;  // always show 0x for %p.
 | ||||
|   // From the POSIX description of '#' flag:
 | ||||
|   //   "For x or X conversion specifiers, a non-zero result shall have
 | ||||
|   //   0x (or 0X) prefixed to it."
 | ||||
|   if (alt && radix == 16 && !info.digits().empty()) { | ||||
|     if (conv.conv().upper()) return "0X"; | ||||
|     return "0x"; | ||||
|   } | ||||
|   return {}; | ||||
| } | ||||
| 
 | ||||
| string_view SignColumn(bool neg, const ConversionSpec &conv) { | ||||
|   if (conv.conv().is_signed()) { | ||||
|     if (neg) return "-"; | ||||
|     if (conv.flags().show_pos) return "+"; | ||||
|     if (conv.flags().sign_col) return " "; | ||||
|   } | ||||
|   return {}; | ||||
| } | ||||
| 
 | ||||
| bool ConvertCharImpl(unsigned char v, const ConversionSpec &conv, | ||||
|                      FormatSinkImpl *sink) { | ||||
|   size_t fill = 0; | ||||
|   if (conv.width() >= 0) fill = conv.width(); | ||||
|   ReducePadding(1, &fill); | ||||
|   if (!conv.flags().left) sink->Append(fill, ' '); | ||||
|   sink->Append(1, v); | ||||
|   if (conv.flags().left) sink->Append(fill, ' '); | ||||
|   return true; | ||||
| } | ||||
| 
 | ||||
| bool ConvertIntImplInner(const ConvertedIntInfo &info, | ||||
|                          const ConversionSpec &conv, FormatSinkImpl *sink) { | ||||
|   // Print as a sequence of Substrings:
 | ||||
|   //   [left_spaces][sign][base_indicator][zeroes][formatted][right_spaces]
 | ||||
|   size_t fill = 0; | ||||
|   if (conv.width() >= 0) fill = conv.width(); | ||||
| 
 | ||||
|   string_view formatted = info.digits(); | ||||
|   ReducePadding(formatted, &fill); | ||||
| 
 | ||||
|   string_view sign = SignColumn(info.is_neg(), conv); | ||||
|   ReducePadding(sign, &fill); | ||||
| 
 | ||||
|   string_view base_indicator = BaseIndicator(info, conv); | ||||
|   ReducePadding(base_indicator, &fill); | ||||
| 
 | ||||
|   int precision = conv.precision(); | ||||
|   bool precision_specified = precision >= 0; | ||||
|   if (!precision_specified) | ||||
|     precision = 1; | ||||
| 
 | ||||
|   if (conv.flags().alt && conv.conv().id() == ConversionChar::o) { | ||||
|     // From POSIX description of the '#' (alt) flag:
 | ||||
|     //   "For o conversion, it increases the precision (if necessary) to
 | ||||
|     //   force the first digit of the result to be zero."
 | ||||
|     if (formatted.empty() || *formatted.begin() != '0') { | ||||
|       int needed = static_cast<int>(formatted.size()) + 1; | ||||
|       precision = std::max(precision, needed); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   size_t num_zeroes = Excess(formatted.size(), precision); | ||||
|   ReducePadding(num_zeroes, &fill); | ||||
| 
 | ||||
|   size_t num_left_spaces = !conv.flags().left ? fill : 0; | ||||
|   size_t num_right_spaces = conv.flags().left ? fill : 0; | ||||
| 
 | ||||
|   // From POSIX description of the '0' (zero) flag:
 | ||||
|   //   "For d, i, o, u, x, and X conversion specifiers, if a precision
 | ||||
|   //   is specified, the '0' flag is ignored."
 | ||||
|   if (!precision_specified && conv.flags().zero) { | ||||
|     num_zeroes += num_left_spaces; | ||||
|     num_left_spaces = 0; | ||||
|   } | ||||
| 
 | ||||
|   sink->Append(num_left_spaces, ' '); | ||||
|   sink->Append(sign); | ||||
|   sink->Append(base_indicator); | ||||
|   sink->Append(num_zeroes, '0'); | ||||
|   sink->Append(formatted); | ||||
|   sink->Append(num_right_spaces, ' '); | ||||
|   return true; | ||||
| } | ||||
| 
 | ||||
| template <typename T> | ||||
| bool ConvertIntImplInner(T v, const ConversionSpec &conv, | ||||
|                          FormatSinkImpl *sink) { | ||||
|   ConvertedIntInfo info(v, conv.conv()); | ||||
|   if (conv.flags().basic && conv.conv().id() != ConversionChar::p) { | ||||
|     if (info.is_neg()) sink->Append(1, '-'); | ||||
|     if (info.digits().empty()) { | ||||
|       sink->Append(1, '0'); | ||||
|     } else { | ||||
|       sink->Append(info.digits()); | ||||
|     } | ||||
|     return true; | ||||
|   } | ||||
|   return ConvertIntImplInner(info, conv, sink); | ||||
| } | ||||
| 
 | ||||
| template <typename T> | ||||
| bool ConvertIntArg(T v, const ConversionSpec &conv, FormatSinkImpl *sink) { | ||||
|   if (conv.conv().is_float()) { | ||||
|     return FormatConvertImpl(static_cast<double>(v), conv, sink).value; | ||||
|   } | ||||
|   if (conv.conv().id() == ConversionChar::c) | ||||
|     return ConvertCharImpl(static_cast<unsigned char>(v), conv, sink); | ||||
|   if (!conv.conv().is_integral()) | ||||
|     return false; | ||||
|   if (!conv.conv().is_signed() && IsSigned<T>::value) { | ||||
|     using U = typename MakeUnsigned<T>::type; | ||||
|     return FormatConvertImpl(static_cast<U>(v), conv, sink).value; | ||||
|   } | ||||
|   return ConvertIntImplInner(v, conv, sink); | ||||
| } | ||||
| 
 | ||||
| template <typename T> | ||||
| bool ConvertFloatArg(T v, const ConversionSpec &conv, FormatSinkImpl *sink) { | ||||
|   return conv.conv().is_float() && ConvertFloatImpl(v, conv, sink); | ||||
| } | ||||
| 
 | ||||
| inline bool ConvertStringArg(string_view v, const ConversionSpec &conv, | ||||
|                              FormatSinkImpl *sink) { | ||||
|   if (conv.conv().id() != ConversionChar::s) | ||||
|     return false; | ||||
|   if (conv.flags().basic) { | ||||
|     sink->Append(v); | ||||
|     return true; | ||||
|   } | ||||
|   return sink->PutPaddedString(v, conv.width(), conv.precision(), | ||||
|                                conv.flags().left); | ||||
| } | ||||
| 
 | ||||
| }  // namespace
 | ||||
| 
 | ||||
| // ==================== Strings ====================
 | ||||
| ConvertResult<Conv::s> FormatConvertImpl(const std::string &v, | ||||
|                                          const ConversionSpec &conv, | ||||
|                                          FormatSinkImpl *sink) { | ||||
|   return {ConvertStringArg(v, conv, sink)}; | ||||
| } | ||||
| 
 | ||||
| ConvertResult<Conv::s> FormatConvertImpl(string_view v, | ||||
|                                          const ConversionSpec &conv, | ||||
|                                          FormatSinkImpl *sink) { | ||||
|   return {ConvertStringArg(v, conv, sink)}; | ||||
| } | ||||
| 
 | ||||
| ConvertResult<Conv::s | Conv::p> FormatConvertImpl(const char *v, | ||||
|                                                    const ConversionSpec &conv, | ||||
|                                                    FormatSinkImpl *sink) { | ||||
|   if (conv.conv().id() == ConversionChar::p) | ||||
|     return {FormatConvertImpl(VoidPtr(v), conv, sink).value}; | ||||
|   size_t len; | ||||
|   if (v == nullptr) { | ||||
|     len = 0; | ||||
|   } else if (conv.precision() < 0) { | ||||
|     len = std::strlen(v); | ||||
|   } else { | ||||
|     // If precision is set, we look for the null terminator on the valid range.
 | ||||
|     len = std::find(v, v + conv.precision(), '\0') - v; | ||||
|   } | ||||
|   return {ConvertStringArg(string_view(v, len), conv, sink)}; | ||||
| } | ||||
| 
 | ||||
| // ==================== Raw pointers ====================
 | ||||
| ConvertResult<Conv::p> FormatConvertImpl(VoidPtr v, const ConversionSpec &conv, | ||||
|                                          FormatSinkImpl *sink) { | ||||
|   if (conv.conv().id() != ConversionChar::p) | ||||
|     return {false}; | ||||
|   if (!v.value) { | ||||
|     sink->Append("(nil)"); | ||||
|     return {true}; | ||||
|   } | ||||
|   return {ConvertIntImplInner(v.value, conv, sink)}; | ||||
| } | ||||
| 
 | ||||
| // ==================== Floats ====================
 | ||||
| FloatingConvertResult FormatConvertImpl(float v, const ConversionSpec &conv, | ||||
|                                         FormatSinkImpl *sink) { | ||||
|   return {ConvertFloatArg(v, conv, sink)}; | ||||
| } | ||||
| FloatingConvertResult FormatConvertImpl(double v, const ConversionSpec &conv, | ||||
|                                         FormatSinkImpl *sink) { | ||||
|   return {ConvertFloatArg(v, conv, sink)}; | ||||
| } | ||||
| FloatingConvertResult FormatConvertImpl(long double v, | ||||
|                                         const ConversionSpec &conv, | ||||
|                                         FormatSinkImpl *sink) { | ||||
|   return {ConvertFloatArg(v, conv, sink)}; | ||||
| } | ||||
| 
 | ||||
| // ==================== Chars ====================
 | ||||
| IntegralConvertResult FormatConvertImpl(char v, const ConversionSpec &conv, | ||||
|                                         FormatSinkImpl *sink) { | ||||
|   return {ConvertIntArg(v, conv, sink)}; | ||||
| } | ||||
| IntegralConvertResult FormatConvertImpl(signed char v, | ||||
|                                         const ConversionSpec &conv, | ||||
|                                         FormatSinkImpl *sink) { | ||||
|   return {ConvertIntArg(v, conv, sink)}; | ||||
| } | ||||
| IntegralConvertResult FormatConvertImpl(unsigned char v, | ||||
|                                         const ConversionSpec &conv, | ||||
|                                         FormatSinkImpl *sink) { | ||||
|   return {ConvertIntArg(v, conv, sink)}; | ||||
| } | ||||
| 
 | ||||
| // ==================== Ints ====================
 | ||||
| IntegralConvertResult FormatConvertImpl(short v,  // NOLINT
 | ||||
|                                         const ConversionSpec &conv, | ||||
|                                         FormatSinkImpl *sink) { | ||||
|   return {ConvertIntArg(v, conv, sink)}; | ||||
| } | ||||
| IntegralConvertResult FormatConvertImpl(unsigned short v,  // NOLINT
 | ||||
|                                         const ConversionSpec &conv, | ||||
|                                         FormatSinkImpl *sink) { | ||||
|   return {ConvertIntArg(v, conv, sink)}; | ||||
| } | ||||
| IntegralConvertResult FormatConvertImpl(int v, const ConversionSpec &conv, | ||||
|                                         FormatSinkImpl *sink) { | ||||
|   return {ConvertIntArg(v, conv, sink)}; | ||||
| } | ||||
| IntegralConvertResult FormatConvertImpl(unsigned v, const ConversionSpec &conv, | ||||
|                                         FormatSinkImpl *sink) { | ||||
|   return {ConvertIntArg(v, conv, sink)}; | ||||
| } | ||||
| IntegralConvertResult FormatConvertImpl(long v,  // NOLINT
 | ||||
|                                         const ConversionSpec &conv, | ||||
|                                         FormatSinkImpl *sink) { | ||||
|   return {ConvertIntArg(v, conv, sink)}; | ||||
| } | ||||
| IntegralConvertResult FormatConvertImpl(unsigned long v,  // NOLINT
 | ||||
|                                         const ConversionSpec &conv, | ||||
|                                         FormatSinkImpl *sink) { | ||||
|   return {ConvertIntArg(v, conv, sink)}; | ||||
| } | ||||
| IntegralConvertResult FormatConvertImpl(long long v,  // NOLINT
 | ||||
|                                         const ConversionSpec &conv, | ||||
|                                         FormatSinkImpl *sink) { | ||||
|   return {ConvertIntArg(v, conv, sink)}; | ||||
| } | ||||
| IntegralConvertResult FormatConvertImpl(unsigned long long v,  // NOLINT
 | ||||
|                                         const ConversionSpec &conv, | ||||
|                                         FormatSinkImpl *sink) { | ||||
|   return {ConvertIntArg(v, conv, sink)}; | ||||
| } | ||||
| IntegralConvertResult FormatConvertImpl(absl::uint128 v, | ||||
|                                         const ConversionSpec &conv, | ||||
|                                         FormatSinkImpl *sink) { | ||||
|   return {ConvertIntArg(v, conv, sink)}; | ||||
| } | ||||
| 
 | ||||
| template struct FormatArgImpl::TypedVTable<str_format_internal::VoidPtr>; | ||||
| 
 | ||||
| template struct FormatArgImpl::TypedVTable<bool>; | ||||
| template struct FormatArgImpl::TypedVTable<char>; | ||||
| template struct FormatArgImpl::TypedVTable<signed char>; | ||||
| template struct FormatArgImpl::TypedVTable<unsigned char>; | ||||
| template struct FormatArgImpl::TypedVTable<short>;           // NOLINT
 | ||||
| template struct FormatArgImpl::TypedVTable<unsigned short>;  // NOLINT
 | ||||
| template struct FormatArgImpl::TypedVTable<int>; | ||||
| template struct FormatArgImpl::TypedVTable<unsigned>; | ||||
| template struct FormatArgImpl::TypedVTable<long>;                // NOLINT
 | ||||
| template struct FormatArgImpl::TypedVTable<unsigned long>;       // NOLINT
 | ||||
| template struct FormatArgImpl::TypedVTable<long long>;           // NOLINT
 | ||||
| template struct FormatArgImpl::TypedVTable<unsigned long long>;  // NOLINT
 | ||||
| template struct FormatArgImpl::TypedVTable<absl::uint128>; | ||||
| 
 | ||||
| template struct FormatArgImpl::TypedVTable<float>; | ||||
| template struct FormatArgImpl::TypedVTable<double>; | ||||
| template struct FormatArgImpl::TypedVTable<long double>; | ||||
| 
 | ||||
| template struct FormatArgImpl::TypedVTable<const char *>; | ||||
| template struct FormatArgImpl::TypedVTable<std::string>; | ||||
| template struct FormatArgImpl::TypedVTable<string_view>; | ||||
| 
 | ||||
| }  // namespace str_format_internal
 | ||||
| 
 | ||||
| }  // namespace absl
 | ||||
							
								
								
									
										434
									
								
								absl/strings/internal/str_format/arg.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										434
									
								
								absl/strings/internal/str_format/arg.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,434 @@ | |||
| #ifndef ABSL_STRINGS_INTERNAL_STR_FORMAT_ARG_H_ | ||||
| #define ABSL_STRINGS_INTERNAL_STR_FORMAT_ARG_H_ | ||||
| 
 | ||||
| #include <string.h> | ||||
| #include <wchar.h> | ||||
| 
 | ||||
| #include <cstdio> | ||||
| #include <iomanip> | ||||
| #include <limits> | ||||
| #include <sstream> | ||||
| #include <string> | ||||
| #include <type_traits> | ||||
| 
 | ||||
| #include "absl/base/port.h" | ||||
| #include "absl/meta/type_traits.h" | ||||
| #include "absl/numeric/int128.h" | ||||
| #include "absl/strings/internal/str_format/extension.h" | ||||
| #include "absl/strings/string_view.h" | ||||
| 
 | ||||
| class Cord; | ||||
| class CordReader; | ||||
| 
 | ||||
| namespace absl { | ||||
| 
 | ||||
| class FormatCountCapture; | ||||
| class FormatSink; | ||||
| 
 | ||||
| namespace str_format_internal { | ||||
| 
 | ||||
| template <typename T, typename = void> | ||||
| struct HasUserDefinedConvert : std::false_type {}; | ||||
| 
 | ||||
| template <typename T> | ||||
| struct HasUserDefinedConvert< | ||||
|     T, void_t<decltype(AbslFormatConvert( | ||||
|            std::declval<const T&>(), std::declval<const ConversionSpec&>(), | ||||
|            std::declval<FormatSink*>()))>> : std::true_type {}; | ||||
| template <typename T> | ||||
| class StreamedWrapper; | ||||
| 
 | ||||
| // If 'v' can be converted (in the printf sense) according to 'conv',
 | ||||
| // then convert it, appending to `sink` and return `true`.
 | ||||
| // Otherwise fail and return `false`.
 | ||||
| // Raw pointers.
 | ||||
| struct VoidPtr { | ||||
|   VoidPtr() = default; | ||||
|   template <typename T, | ||||
|             decltype(reinterpret_cast<uintptr_t>(std::declval<T*>())) = 0> | ||||
|   VoidPtr(T* ptr)  // NOLINT
 | ||||
|       : value(ptr ? reinterpret_cast<uintptr_t>(ptr) : 0) {} | ||||
|   uintptr_t value; | ||||
| }; | ||||
| ConvertResult<Conv::p> FormatConvertImpl(VoidPtr v, const ConversionSpec& conv, | ||||
|                                          FormatSinkImpl* sink); | ||||
| 
 | ||||
| // Strings.
 | ||||
| ConvertResult<Conv::s> FormatConvertImpl(const std::string& v, | ||||
|                                          const ConversionSpec& conv, | ||||
|                                          FormatSinkImpl* sink); | ||||
| ConvertResult<Conv::s> FormatConvertImpl(string_view v, | ||||
|                                          const ConversionSpec& conv, | ||||
|                                          FormatSinkImpl* sink); | ||||
| ConvertResult<Conv::s | Conv::p> FormatConvertImpl(const char* v, | ||||
|                                                    const ConversionSpec& conv, | ||||
|                                                    FormatSinkImpl* sink); | ||||
| template <class AbslCord, | ||||
|           typename std::enable_if< | ||||
|               std::is_same<AbslCord, ::Cord>::value>::type* = nullptr, | ||||
|           class AbslCordReader = ::CordReader> | ||||
| ConvertResult<Conv::s> FormatConvertImpl(const AbslCord& value, | ||||
|                                          const ConversionSpec& conv, | ||||
|                                          FormatSinkImpl* sink) { | ||||
|   if (conv.conv().id() != ConversionChar::s) return {false}; | ||||
| 
 | ||||
|   bool is_left = conv.flags().left; | ||||
|   size_t space_remaining = 0; | ||||
| 
 | ||||
|   int width = conv.width(); | ||||
|   if (width >= 0) space_remaining = width; | ||||
| 
 | ||||
|   size_t to_write = value.size(); | ||||
| 
 | ||||
|   int precision = conv.precision(); | ||||
|   if (precision >= 0) | ||||
|     to_write = std::min(to_write, static_cast<size_t>(precision)); | ||||
| 
 | ||||
|   space_remaining = Excess(to_write, space_remaining); | ||||
| 
 | ||||
|   if (space_remaining > 0 && !is_left) sink->Append(space_remaining, ' '); | ||||
| 
 | ||||
|   string_view piece; | ||||
|   for (AbslCordReader reader(value); | ||||
|        to_write > 0 && reader.ReadFragment(&piece); to_write -= piece.size()) { | ||||
|     if (piece.size() > to_write) piece.remove_suffix(piece.size() - to_write); | ||||
|     sink->Append(piece); | ||||
|   } | ||||
| 
 | ||||
|   if (space_remaining > 0 && is_left) sink->Append(space_remaining, ' '); | ||||
|   return {true}; | ||||
| } | ||||
| 
 | ||||
| using IntegralConvertResult = | ||||
|     ConvertResult<Conv::c | Conv::numeric | Conv::star>; | ||||
| using FloatingConvertResult = ConvertResult<Conv::floating>; | ||||
| 
 | ||||
| // Floats.
 | ||||
| FloatingConvertResult FormatConvertImpl(float v, const ConversionSpec& conv, | ||||
|                                         FormatSinkImpl* sink); | ||||
| FloatingConvertResult FormatConvertImpl(double v, const ConversionSpec& conv, | ||||
|                                         FormatSinkImpl* sink); | ||||
| FloatingConvertResult FormatConvertImpl(long double v, | ||||
|                                         const ConversionSpec& conv, | ||||
|                                         FormatSinkImpl* sink); | ||||
| 
 | ||||
| // Chars.
 | ||||
| IntegralConvertResult FormatConvertImpl(char v, const ConversionSpec& conv, | ||||
|                                         FormatSinkImpl* sink); | ||||
| IntegralConvertResult FormatConvertImpl(signed char v, | ||||
|                                         const ConversionSpec& conv, | ||||
|                                         FormatSinkImpl* sink); | ||||
| IntegralConvertResult FormatConvertImpl(unsigned char v, | ||||
|                                         const ConversionSpec& conv, | ||||
|                                         FormatSinkImpl* sink); | ||||
| 
 | ||||
| // Ints.
 | ||||
| IntegralConvertResult FormatConvertImpl(short v,  // NOLINT
 | ||||
|                                         const ConversionSpec& conv, | ||||
|                                         FormatSinkImpl* sink); | ||||
| IntegralConvertResult FormatConvertImpl(unsigned short v,  // NOLINT
 | ||||
|                                         const ConversionSpec& conv, | ||||
|                                         FormatSinkImpl* sink); | ||||
| IntegralConvertResult FormatConvertImpl(int v, const ConversionSpec& conv, | ||||
|                                         FormatSinkImpl* sink); | ||||
| IntegralConvertResult FormatConvertImpl(unsigned v, const ConversionSpec& conv, | ||||
|                                         FormatSinkImpl* sink); | ||||
| IntegralConvertResult FormatConvertImpl(long v,  // NOLINT
 | ||||
|                                         const ConversionSpec& conv, | ||||
|                                         FormatSinkImpl* sink); | ||||
| IntegralConvertResult FormatConvertImpl(unsigned long v,  // NOLINT
 | ||||
|                                         const ConversionSpec& conv, | ||||
|                                         FormatSinkImpl* sink); | ||||
| IntegralConvertResult FormatConvertImpl(long long v,  // NOLINT
 | ||||
|                                         const ConversionSpec& conv, | ||||
|                                         FormatSinkImpl* sink); | ||||
| IntegralConvertResult FormatConvertImpl(unsigned long long v,  // NOLINT
 | ||||
|                                         const ConversionSpec& conv, | ||||
|                                         FormatSinkImpl* sink); | ||||
| IntegralConvertResult FormatConvertImpl(uint128 v, const ConversionSpec& conv, | ||||
|                                         FormatSinkImpl* sink); | ||||
| template <typename T, enable_if_t<std::is_same<T, bool>::value, int> = 0> | ||||
| IntegralConvertResult FormatConvertImpl(T v, const ConversionSpec& conv, | ||||
|                                         FormatSinkImpl* sink) { | ||||
|   return FormatConvertImpl(static_cast<int>(v), conv, sink); | ||||
| } | ||||
| 
 | ||||
| // We provide this function to help the checker, but it is never defined.
 | ||||
| // FormatArgImpl will use the underlying Convert functions instead.
 | ||||
| template <typename T> | ||||
| typename std::enable_if<std::is_enum<T>::value && | ||||
|                             !HasUserDefinedConvert<T>::value, | ||||
|                         IntegralConvertResult>::type | ||||
| FormatConvertImpl(T v, const ConversionSpec& conv, FormatSinkImpl* sink); | ||||
| 
 | ||||
| template <typename T> | ||||
| ConvertResult<Conv::s> FormatConvertImpl(const StreamedWrapper<T>& v, | ||||
|                                          const ConversionSpec& conv, | ||||
|                                          FormatSinkImpl* out) { | ||||
|   std::ostringstream oss; | ||||
|   oss << v.v_; | ||||
|   if (!oss) return {false}; | ||||
|   return str_format_internal::FormatConvertImpl(oss.str(), conv, out); | ||||
| } | ||||
| 
 | ||||
| // Use templates and dependent types to delay evaluation of the function
 | ||||
| // until after FormatCountCapture is fully defined.
 | ||||
| struct FormatCountCaptureHelper { | ||||
|   template <class T = int> | ||||
|   static ConvertResult<Conv::n> ConvertHelper(const FormatCountCapture& v, | ||||
|                                               const ConversionSpec& conv, | ||||
|                                               FormatSinkImpl* sink) { | ||||
|     const absl::enable_if_t<sizeof(T) != 0, FormatCountCapture>& v2 = v; | ||||
| 
 | ||||
|     if (conv.conv().id() != str_format_internal::ConversionChar::n) | ||||
|       return {false}; | ||||
|     *v2.p_ = static_cast<int>(sink->size()); | ||||
|     return {true}; | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| template <class T = int> | ||||
| ConvertResult<Conv::n> FormatConvertImpl(const FormatCountCapture& v, | ||||
|                                          const ConversionSpec& conv, | ||||
|                                          FormatSinkImpl* sink) { | ||||
|   return FormatCountCaptureHelper::ConvertHelper(v, conv, sink); | ||||
| } | ||||
| 
 | ||||
| // Helper friend struct to hide implementation details from the public API of
 | ||||
| // FormatArgImpl.
 | ||||
| struct FormatArgImplFriend { | ||||
|   template <typename Arg> | ||||
|   static bool ToInt(Arg arg, int* out) { | ||||
|     if (!arg.vtbl_->to_int) return false; | ||||
|     *out = arg.vtbl_->to_int(arg.data_); | ||||
|     return true; | ||||
|   } | ||||
| 
 | ||||
|   template <typename Arg> | ||||
|   static bool Convert(Arg arg, const str_format_internal::ConversionSpec& conv, | ||||
|                       FormatSinkImpl* out) { | ||||
|     return arg.vtbl_->convert(arg.data_, conv, out); | ||||
|   } | ||||
| 
 | ||||
|   template <typename Arg> | ||||
|   static const void* GetVTablePtrForTest(Arg arg) { | ||||
|     return arg.vtbl_; | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| // A type-erased handle to a format argument.
 | ||||
| class FormatArgImpl { | ||||
|  private: | ||||
|   enum { kInlinedSpace = 8 }; | ||||
| 
 | ||||
|   using VoidPtr = str_format_internal::VoidPtr; | ||||
| 
 | ||||
|   union Data { | ||||
|     const void* ptr; | ||||
|     const volatile void* volatile_ptr; | ||||
|     char buf[kInlinedSpace]; | ||||
|   }; | ||||
| 
 | ||||
|   struct VTable { | ||||
|     bool (*convert)(Data, const str_format_internal::ConversionSpec& conv, | ||||
|                     FormatSinkImpl* out); | ||||
|     int (*to_int)(Data); | ||||
|   }; | ||||
| 
 | ||||
|   template <typename T> | ||||
|   struct store_by_value | ||||
|       : std::integral_constant<bool, (sizeof(T) <= kInlinedSpace) && | ||||
|                                          (std::is_integral<T>::value || | ||||
|                                           std::is_floating_point<T>::value || | ||||
|                                           std::is_pointer<T>::value || | ||||
|                                           std::is_same<VoidPtr, T>::value)> {}; | ||||
| 
 | ||||
|   enum StoragePolicy { ByPointer, ByVolatilePointer, ByValue }; | ||||
|   template <typename T> | ||||
|   struct storage_policy | ||||
|       : std::integral_constant<StoragePolicy, | ||||
|                                (std::is_volatile<T>::value | ||||
|                                     ? ByVolatilePointer | ||||
|                                     : (store_by_value<T>::value ? ByValue | ||||
|                                                                 : ByPointer))> { | ||||
|   }; | ||||
| 
 | ||||
|   // An instance of an FormatArgImpl::VTable suitable for 'T'.
 | ||||
|   template <typename T> | ||||
|   struct TypedVTable; | ||||
| 
 | ||||
|   // To reduce the number of vtables we will decay values before hand.
 | ||||
|   // Anything with a user-defined Convert will get its own vtable.
 | ||||
|   // For everything else:
 | ||||
|   //   - Decay char* and char arrays into `const char*`
 | ||||
|   //   - Decay any other pointer to `const void*`
 | ||||
|   //   - Decay all enums to their underlying type.
 | ||||
|   //   - Decay function pointers to void*.
 | ||||
|   template <typename T, typename = void> | ||||
|   struct DecayType { | ||||
|     static constexpr bool kHasUserDefined = | ||||
|         str_format_internal::HasUserDefinedConvert<T>::value; | ||||
|     using type = typename std::conditional< | ||||
|         !kHasUserDefined && std::is_convertible<T, const char*>::value, | ||||
|         const char*, | ||||
|         typename std::conditional<!kHasUserDefined && | ||||
|                                       std::is_convertible<T, VoidPtr>::value, | ||||
|                                   VoidPtr, const T&>::type>::type; | ||||
|   }; | ||||
|   template <typename T> | ||||
|   struct DecayType<T, | ||||
|                    typename std::enable_if< | ||||
|                        !str_format_internal::HasUserDefinedConvert<T>::value && | ||||
|                        std::is_enum<T>::value>::type> { | ||||
|     using type = typename std::underlying_type<T>::type; | ||||
|   }; | ||||
| 
 | ||||
|  public: | ||||
|   template <typename T> | ||||
|   explicit FormatArgImpl(const T& value) { | ||||
|     using D = typename DecayType<T>::type; | ||||
|     static_assert( | ||||
|         std::is_same<D, const T&>::value || storage_policy<D>::value == ByValue, | ||||
|         "Decayed types must be stored by value"); | ||||
|     Init(static_cast<D>(value)); | ||||
|   } | ||||
| 
 | ||||
|  private: | ||||
|   friend struct str_format_internal::FormatArgImplFriend; | ||||
|   template <typename T, StoragePolicy = storage_policy<T>::value> | ||||
|   struct Manager; | ||||
| 
 | ||||
|   template <typename T> | ||||
|   struct Manager<T, ByPointer> { | ||||
|     static Data SetValue(const T& value) { | ||||
|       Data data; | ||||
|       data.ptr = &value; | ||||
|       return data; | ||||
|     } | ||||
| 
 | ||||
|     static const T& Value(Data arg) { return *static_cast<const T*>(arg.ptr); } | ||||
|   }; | ||||
| 
 | ||||
|   template <typename T> | ||||
|   struct Manager<T, ByVolatilePointer> { | ||||
|     static Data SetValue(const T& value) { | ||||
|       Data data; | ||||
|       data.volatile_ptr = &value; | ||||
|       return data; | ||||
|     } | ||||
| 
 | ||||
|     static const T& Value(Data arg) { | ||||
|       return *static_cast<const T*>(arg.volatile_ptr); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   template <typename T> | ||||
|   struct Manager<T, ByValue> { | ||||
|     static Data SetValue(const T& value) { | ||||
|       Data data; | ||||
|       memcpy(data.buf, &value, sizeof(value)); | ||||
|       return data; | ||||
|     } | ||||
| 
 | ||||
|     static T Value(Data arg) { | ||||
|       T value; | ||||
|       memcpy(&value, arg.buf, sizeof(T)); | ||||
|       return value; | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   template <typename T> | ||||
|   void Init(const T& value); | ||||
| 
 | ||||
|   template <typename T> | ||||
|   static int ToIntVal(const T& val) { | ||||
|     using CommonType = typename std::conditional<std::is_signed<T>::value, | ||||
|                                                  int64_t, uint64_t>::type; | ||||
|     if (static_cast<CommonType>(val) > | ||||
|         static_cast<CommonType>(std::numeric_limits<int>::max())) { | ||||
|       return std::numeric_limits<int>::max(); | ||||
|     } else if (std::is_signed<T>::value && | ||||
|                static_cast<CommonType>(val) < | ||||
|                    static_cast<CommonType>(std::numeric_limits<int>::min())) { | ||||
|       return std::numeric_limits<int>::min(); | ||||
|     } | ||||
|     return static_cast<int>(val); | ||||
|   } | ||||
| 
 | ||||
|   Data data_; | ||||
|   const VTable* vtbl_; | ||||
| }; | ||||
| 
 | ||||
| template <typename T> | ||||
| struct FormatArgImpl::TypedVTable { | ||||
|  private: | ||||
|   static bool ConvertImpl(Data arg, | ||||
|                           const str_format_internal::ConversionSpec& conv, | ||||
|                           FormatSinkImpl* out) { | ||||
|     return str_format_internal::FormatConvertImpl(Manager<T>::Value(arg), conv, | ||||
|                                                   out) | ||||
|         .value; | ||||
|   } | ||||
| 
 | ||||
|   template <typename U = T, typename = void> | ||||
|   struct ToIntImpl { | ||||
|     static constexpr int (*value)(Data) = nullptr; | ||||
|   }; | ||||
| 
 | ||||
|   template <typename U> | ||||
|   struct ToIntImpl<U, | ||||
|                    typename std::enable_if<std::is_integral<U>::value>::type> { | ||||
|     static int Invoke(Data arg) { return ToIntVal(Manager<T>::Value(arg)); } | ||||
|     static constexpr int (*value)(Data) = &Invoke; | ||||
|   }; | ||||
| 
 | ||||
|   template <typename U> | ||||
|   struct ToIntImpl<U, typename std::enable_if<std::is_enum<U>::value>::type> { | ||||
|     static int Invoke(Data arg) { | ||||
|       return ToIntVal(static_cast<typename std::underlying_type<T>::type>( | ||||
|           Manager<T>::Value(arg))); | ||||
|     } | ||||
|     static constexpr int (*value)(Data) = &Invoke; | ||||
|   }; | ||||
| 
 | ||||
|  public: | ||||
|   static constexpr VTable value{&ConvertImpl, ToIntImpl<>::value}; | ||||
| }; | ||||
| 
 | ||||
| template <typename T> | ||||
| constexpr FormatArgImpl::VTable FormatArgImpl::TypedVTable<T>::value; | ||||
| 
 | ||||
| template <typename T> | ||||
| void FormatArgImpl::Init(const T& value) { | ||||
|   data_ = Manager<T>::SetValue(value); | ||||
|   vtbl_ = &TypedVTable<T>::value; | ||||
| } | ||||
| 
 | ||||
| extern template struct FormatArgImpl::TypedVTable<str_format_internal::VoidPtr>; | ||||
| 
 | ||||
| extern template struct FormatArgImpl::TypedVTable<bool>; | ||||
| extern template struct FormatArgImpl::TypedVTable<char>; | ||||
| extern template struct FormatArgImpl::TypedVTable<signed char>; | ||||
| extern template struct FormatArgImpl::TypedVTable<unsigned char>; | ||||
| extern template struct FormatArgImpl::TypedVTable<short>;           // NOLINT
 | ||||
| extern template struct FormatArgImpl::TypedVTable<unsigned short>;  // NOLINT
 | ||||
| extern template struct FormatArgImpl::TypedVTable<int>; | ||||
| extern template struct FormatArgImpl::TypedVTable<unsigned>; | ||||
| extern template struct FormatArgImpl::TypedVTable<long>;           // NOLINT
 | ||||
| extern template struct FormatArgImpl::TypedVTable<unsigned long>;  // NOLINT
 | ||||
| extern template struct FormatArgImpl::TypedVTable<long long>;      // NOLINT
 | ||||
| extern template struct FormatArgImpl::TypedVTable< | ||||
|     unsigned long long>;  // NOLINT
 | ||||
| extern template struct FormatArgImpl::TypedVTable<uint128>; | ||||
| 
 | ||||
| extern template struct FormatArgImpl::TypedVTable<float>; | ||||
| extern template struct FormatArgImpl::TypedVTable<double>; | ||||
| extern template struct FormatArgImpl::TypedVTable<long double>; | ||||
| 
 | ||||
| extern template struct FormatArgImpl::TypedVTable<const char*>; | ||||
| extern template struct FormatArgImpl::TypedVTable<std::string>; | ||||
| extern template struct FormatArgImpl::TypedVTable<string_view>; | ||||
| }  // namespace str_format_internal
 | ||||
| }  // namespace absl
 | ||||
| 
 | ||||
| #endif  // ABSL_STRINGS_INTERNAL_STR_FORMAT_ARG_H_
 | ||||
							
								
								
									
										111
									
								
								absl/strings/internal/str_format/arg_test.cc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								absl/strings/internal/str_format/arg_test.cc
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,111 @@ | |||
| // Copyright 2017 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
 | ||||
| //
 | ||||
| //      http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| #include "absl/strings/internal/str_format/arg.h" | ||||
| 
 | ||||
| #include <ostream> | ||||
| #include <string> | ||||
| #include "gtest/gtest.h" | ||||
| #include "absl/strings/str_format.h" | ||||
| 
 | ||||
| namespace absl { | ||||
| namespace str_format_internal { | ||||
| namespace { | ||||
| 
 | ||||
| class FormatArgImplTest : public ::testing::Test { | ||||
|  public: | ||||
|   enum Color { kRed, kGreen, kBlue }; | ||||
| 
 | ||||
|   static const char *hi() { return "hi"; } | ||||
| }; | ||||
| 
 | ||||
| TEST_F(FormatArgImplTest, ToInt) { | ||||
|   int out = 0; | ||||
|   EXPECT_TRUE(FormatArgImplFriend::ToInt(FormatArgImpl(1), &out)); | ||||
|   EXPECT_EQ(1, out); | ||||
|   EXPECT_TRUE(FormatArgImplFriend::ToInt(FormatArgImpl(-1), &out)); | ||||
|   EXPECT_EQ(-1, out); | ||||
|   EXPECT_TRUE( | ||||
|       FormatArgImplFriend::ToInt(FormatArgImpl(static_cast<char>(64)), &out)); | ||||
|   EXPECT_EQ(64, out); | ||||
|   EXPECT_TRUE(FormatArgImplFriend::ToInt( | ||||
|       FormatArgImpl(static_cast<unsigned long long>(123456)), &out));  // NOLINT
 | ||||
|   EXPECT_EQ(123456, out); | ||||
|   EXPECT_TRUE(FormatArgImplFriend::ToInt( | ||||
|       FormatArgImpl(static_cast<unsigned long long>(  // NOLINT
 | ||||
|                         std::numeric_limits<int>::max()) + | ||||
|                     1), | ||||
|       &out)); | ||||
|   EXPECT_EQ(std::numeric_limits<int>::max(), out); | ||||
|   EXPECT_TRUE(FormatArgImplFriend::ToInt( | ||||
|       FormatArgImpl(static_cast<long long>(  // NOLINT
 | ||||
|                         std::numeric_limits<int>::min()) - | ||||
|                     10), | ||||
|       &out)); | ||||
|   EXPECT_EQ(std::numeric_limits<int>::min(), out); | ||||
|   EXPECT_TRUE(FormatArgImplFriend::ToInt(FormatArgImpl(false), &out)); | ||||
|   EXPECT_EQ(0, out); | ||||
|   EXPECT_TRUE(FormatArgImplFriend::ToInt(FormatArgImpl(true), &out)); | ||||
|   EXPECT_EQ(1, out); | ||||
|   EXPECT_FALSE(FormatArgImplFriend::ToInt(FormatArgImpl(2.2), &out)); | ||||
|   EXPECT_FALSE(FormatArgImplFriend::ToInt(FormatArgImpl(3.2f), &out)); | ||||
|   EXPECT_FALSE(FormatArgImplFriend::ToInt( | ||||
|       FormatArgImpl(static_cast<int *>(nullptr)), &out)); | ||||
|   EXPECT_FALSE(FormatArgImplFriend::ToInt(FormatArgImpl(hi()), &out)); | ||||
|   EXPECT_FALSE(FormatArgImplFriend::ToInt(FormatArgImpl("hi"), &out)); | ||||
|   EXPECT_TRUE(FormatArgImplFriend::ToInt(FormatArgImpl(kBlue), &out)); | ||||
|   EXPECT_EQ(2, out); | ||||
| } | ||||
| 
 | ||||
| extern const char kMyArray[]; | ||||
| 
 | ||||
| TEST_F(FormatArgImplTest, CharArraysDecayToCharPtr) { | ||||
|   const char* a = ""; | ||||
|   EXPECT_EQ(FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl(a)), | ||||
|             FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl(""))); | ||||
|   EXPECT_EQ(FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl(a)), | ||||
|             FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl("A"))); | ||||
|   EXPECT_EQ(FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl(a)), | ||||
|             FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl("ABC"))); | ||||
|   EXPECT_EQ(FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl(a)), | ||||
|             FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl(kMyArray))); | ||||
| } | ||||
| 
 | ||||
| TEST_F(FormatArgImplTest, OtherPtrDecayToVoidPtr) { | ||||
|   auto expected = FormatArgImplFriend::GetVTablePtrForTest( | ||||
|       FormatArgImpl(static_cast<void *>(nullptr))); | ||||
|   EXPECT_EQ(FormatArgImplFriend::GetVTablePtrForTest( | ||||
|                 FormatArgImpl(static_cast<int *>(nullptr))), | ||||
|             expected); | ||||
|   EXPECT_EQ(FormatArgImplFriend::GetVTablePtrForTest( | ||||
|                 FormatArgImpl(static_cast<volatile int *>(nullptr))), | ||||
|             expected); | ||||
| 
 | ||||
|   auto p = static_cast<void (*)()>([] {}); | ||||
|   EXPECT_EQ(FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl(p)), | ||||
|             expected); | ||||
| } | ||||
| 
 | ||||
| TEST_F(FormatArgImplTest, WorksWithCharArraysOfUnknownSize) { | ||||
|   std::string s; | ||||
|   FormatSinkImpl sink(&s); | ||||
|   ConversionSpec conv; | ||||
|   conv.set_conv(ConversionChar::FromChar('s')); | ||||
|   conv.set_flags(Flags()); | ||||
|   conv.set_width(-1); | ||||
|   conv.set_precision(-1); | ||||
|   EXPECT_TRUE( | ||||
|       FormatArgImplFriend::Convert(FormatArgImpl(kMyArray), conv, &sink)); | ||||
|   sink.Flush(); | ||||
|   EXPECT_EQ("ABCDE", s); | ||||
| } | ||||
| const char kMyArray[] = "ABCDE"; | ||||
| 
 | ||||
| }  // namespace
 | ||||
| }  // namespace str_format_internal
 | ||||
| }  // namespace absl
 | ||||
							
								
								
									
										232
									
								
								absl/strings/internal/str_format/bind.cc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										232
									
								
								absl/strings/internal/str_format/bind.cc
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,232 @@ | |||
| #include "absl/strings/internal/str_format/bind.h" | ||||
| 
 | ||||
| #include <cerrno> | ||||
| #include <limits> | ||||
| #include <sstream> | ||||
| #include <string> | ||||
| 
 | ||||
| namespace absl { | ||||
| namespace str_format_internal { | ||||
| 
 | ||||
| namespace { | ||||
| 
 | ||||
| inline bool BindFromPosition(int position, int* value, | ||||
|                              absl::Span<const FormatArgImpl> pack) { | ||||
|   assert(position > 0); | ||||
|   if (static_cast<size_t>(position) > pack.size()) { | ||||
|     return false; | ||||
|   } | ||||
|   // -1 because positions are 1-based
 | ||||
|   return FormatArgImplFriend::ToInt(pack[position - 1], value); | ||||
| } | ||||
| 
 | ||||
| class ArgContext { | ||||
|  public: | ||||
|   explicit ArgContext(absl::Span<const FormatArgImpl> pack) : pack_(pack) {} | ||||
| 
 | ||||
|   // Fill 'bound' with the results of applying the context's argument pack
 | ||||
|   // to the specified 'props'. We synthesize a BoundConversion by
 | ||||
|   // lining up a UnboundConversion with a user argument. We also
 | ||||
|   // resolve any '*' specifiers for width and precision, so after
 | ||||
|   // this call, 'bound' has all the information it needs to be formatted.
 | ||||
|   // Returns false on failure.
 | ||||
|   bool Bind(const UnboundConversion *props, BoundConversion *bound); | ||||
| 
 | ||||
|  private: | ||||
|   absl::Span<const FormatArgImpl> pack_; | ||||
| }; | ||||
| 
 | ||||
| inline bool ArgContext::Bind(const UnboundConversion* unbound, | ||||
|                              BoundConversion* bound) { | ||||
|   const FormatArgImpl* arg = nullptr; | ||||
|   int arg_position = unbound->arg_position; | ||||
|   if (static_cast<size_t>(arg_position - 1) >= pack_.size()) return false; | ||||
|   arg = &pack_[arg_position - 1];  // 1-based
 | ||||
| 
 | ||||
|   if (!unbound->flags.basic) { | ||||
|     int width = unbound->width.value(); | ||||
|     bool force_left = false; | ||||
|     if (unbound->width.is_from_arg()) { | ||||
|       if (!BindFromPosition(unbound->width.get_from_arg(), &width, pack_)) | ||||
|         return false; | ||||
|       if (width < 0) { | ||||
|         // "A negative field width is taken as a '-' flag followed by a
 | ||||
|         // positive field width."
 | ||||
|         force_left = true; | ||||
|         width = -width; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     int precision = unbound->precision.value(); | ||||
|     if (unbound->precision.is_from_arg()) { | ||||
|       if (!BindFromPosition(unbound->precision.get_from_arg(), &precision, | ||||
|                             pack_)) | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     bound->set_width(width); | ||||
|     bound->set_precision(precision); | ||||
|     bound->set_flags(unbound->flags); | ||||
|     if (force_left) | ||||
|       bound->set_left(true); | ||||
|   } else { | ||||
|     bound->set_flags(unbound->flags); | ||||
|     bound->set_width(-1); | ||||
|     bound->set_precision(-1); | ||||
|   } | ||||
| 
 | ||||
|   bound->set_length_mod(unbound->length_mod); | ||||
|   bound->set_conv(unbound->conv); | ||||
|   bound->set_arg(arg); | ||||
|   return true; | ||||
| } | ||||
| 
 | ||||
| template <typename Converter> | ||||
| class ConverterConsumer { | ||||
|  public: | ||||
|   ConverterConsumer(Converter converter, absl::Span<const FormatArgImpl> pack) | ||||
|       : converter_(converter), arg_context_(pack) {} | ||||
| 
 | ||||
|   bool Append(string_view s) { | ||||
|     converter_.Append(s); | ||||
|     return true; | ||||
|   } | ||||
|   bool ConvertOne(const UnboundConversion& conv, string_view conv_string) { | ||||
|     BoundConversion bound; | ||||
|     if (!arg_context_.Bind(&conv, &bound)) return false; | ||||
|     return converter_.ConvertOne(bound, conv_string); | ||||
|   } | ||||
| 
 | ||||
|  private: | ||||
|   Converter converter_; | ||||
|   ArgContext arg_context_; | ||||
| }; | ||||
| 
 | ||||
| template <typename Converter> | ||||
| bool ConvertAll(const UntypedFormatSpecImpl& format, | ||||
|                 absl::Span<const FormatArgImpl> args, | ||||
|                 const Converter& converter) { | ||||
|   const ParsedFormatBase* pc = format.parsed_conversion(); | ||||
|   if (pc) | ||||
|     return pc->ProcessFormat(ConverterConsumer<Converter>(converter, args)); | ||||
| 
 | ||||
|   return ParseFormatString(format.str(), | ||||
|                            ConverterConsumer<Converter>(converter, args)); | ||||
| } | ||||
| 
 | ||||
| class DefaultConverter { | ||||
|  public: | ||||
|   explicit DefaultConverter(FormatSinkImpl* sink) : sink_(sink) {} | ||||
| 
 | ||||
|   void Append(string_view s) const { sink_->Append(s); } | ||||
| 
 | ||||
|   bool ConvertOne(const BoundConversion& bound, string_view /*conv*/) const { | ||||
|     return FormatArgImplFriend::Convert(*bound.arg(), bound, sink_); | ||||
|   } | ||||
| 
 | ||||
|  private: | ||||
|   FormatSinkImpl* sink_; | ||||
| }; | ||||
| 
 | ||||
| class SummarizingConverter { | ||||
|  public: | ||||
|   explicit SummarizingConverter(FormatSinkImpl* sink) : sink_(sink) {} | ||||
| 
 | ||||
|   void Append(string_view s) const { sink_->Append(s); } | ||||
| 
 | ||||
|   bool ConvertOne(const BoundConversion& bound, string_view /*conv*/) const { | ||||
|     UntypedFormatSpecImpl spec("%d"); | ||||
| 
 | ||||
|     std::ostringstream ss; | ||||
|     ss << "{" << Streamable(spec, {*bound.arg()}) << ":" << bound.flags(); | ||||
|     if (bound.width() >= 0) ss << bound.width(); | ||||
|     if (bound.precision() >= 0) ss << "." << bound.precision(); | ||||
|     ss << bound.length_mod() << bound.conv() << "}"; | ||||
|     Append(ss.str()); | ||||
|     return true; | ||||
|   } | ||||
| 
 | ||||
|  private: | ||||
|   FormatSinkImpl* sink_; | ||||
| }; | ||||
| 
 | ||||
| }  // namespace
 | ||||
| 
 | ||||
| bool BindWithPack(const UnboundConversion* props, | ||||
|                   absl::Span<const FormatArgImpl> pack, | ||||
|                   BoundConversion* bound) { | ||||
|   return ArgContext(pack).Bind(props, bound); | ||||
| } | ||||
| 
 | ||||
| std::string Summarize(const UntypedFormatSpecImpl& format, | ||||
|                  absl::Span<const FormatArgImpl> args) { | ||||
|   typedef SummarizingConverter Converter; | ||||
|   std::string out; | ||||
|   { | ||||
|     // inner block to destroy sink before returning out. It ensures a last
 | ||||
|     // flush.
 | ||||
|     FormatSinkImpl sink(&out); | ||||
|     if (!ConvertAll(format, args, Converter(&sink))) { | ||||
|       sink.Flush(); | ||||
|       out.clear(); | ||||
|     } | ||||
|   } | ||||
|   return out; | ||||
| } | ||||
| 
 | ||||
| bool FormatUntyped(FormatRawSinkImpl raw_sink, | ||||
|                    const UntypedFormatSpecImpl& format, | ||||
|                    absl::Span<const FormatArgImpl> args) { | ||||
|   FormatSinkImpl sink(raw_sink); | ||||
|   using Converter = DefaultConverter; | ||||
|   if (!ConvertAll(format, args, Converter(&sink))) { | ||||
|     sink.Flush(); | ||||
|     return false; | ||||
|   } | ||||
|   return true; | ||||
| } | ||||
| 
 | ||||
| std::ostream& Streamable::Print(std::ostream& os) const { | ||||
|   if (!FormatUntyped(&os, format_, args_)) os.setstate(std::ios::failbit); | ||||
|   return os; | ||||
| } | ||||
| 
 | ||||
| std::string& AppendPack(std::string* out, const UntypedFormatSpecImpl& format, | ||||
|                    absl::Span<const FormatArgImpl> args) { | ||||
|   size_t orig = out->size(); | ||||
|   if (!FormatUntyped(out, format, args)) out->resize(orig); | ||||
|   return *out; | ||||
| } | ||||
| 
 | ||||
| int FprintF(std::FILE* output, const UntypedFormatSpecImpl& format, | ||||
|             absl::Span<const FormatArgImpl> args) { | ||||
|   FILERawSink sink(output); | ||||
|   if (!FormatUntyped(&sink, format, args)) { | ||||
|     errno = EINVAL; | ||||
|     return -1; | ||||
|   } | ||||
|   if (sink.error()) { | ||||
|     errno = sink.error(); | ||||
|     return -1; | ||||
|   } | ||||
|   if (sink.count() > std::numeric_limits<int>::max()) { | ||||
|     errno = EFBIG; | ||||
|     return -1; | ||||
|   } | ||||
|   return static_cast<int>(sink.count()); | ||||
| } | ||||
| 
 | ||||
| int SnprintF(char* output, size_t size, const UntypedFormatSpecImpl& format, | ||||
|              absl::Span<const FormatArgImpl> args) { | ||||
|   BufferRawSink sink(output, size ? size - 1 : 0); | ||||
|   if (!FormatUntyped(&sink, format, args)) { | ||||
|     errno = EINVAL; | ||||
|     return -1; | ||||
|   } | ||||
|   size_t total = sink.total_written(); | ||||
|   if (size) output[std::min(total, size - 1)] = 0; | ||||
|   return static_cast<int>(total); | ||||
| } | ||||
| 
 | ||||
| }  // namespace str_format_internal
 | ||||
| }  // namespace absl
 | ||||
							
								
								
									
										189
									
								
								absl/strings/internal/str_format/bind.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										189
									
								
								absl/strings/internal/str_format/bind.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,189 @@ | |||
| #ifndef ABSL_STRINGS_INTERNAL_STR_FORMAT_BIND_H_ | ||||
| #define ABSL_STRINGS_INTERNAL_STR_FORMAT_BIND_H_ | ||||
| 
 | ||||
| #include <array> | ||||
| #include <cstdio> | ||||
| #include <sstream> | ||||
| #include <string> | ||||
| 
 | ||||
| #include "absl/base/port.h" | ||||
| #include "absl/container/inlined_vector.h" | ||||
| #include "absl/strings/internal/str_format/arg.h" | ||||
| #include "absl/strings/internal/str_format/checker.h" | ||||
| #include "absl/strings/internal/str_format/parser.h" | ||||
| #include "absl/types/span.h" | ||||
| 
 | ||||
| namespace absl { | ||||
| 
 | ||||
| class UntypedFormatSpec; | ||||
| 
 | ||||
| namespace str_format_internal { | ||||
| 
 | ||||
| class BoundConversion : public ConversionSpec { | ||||
|  public: | ||||
|   const FormatArgImpl* arg() const { return arg_; } | ||||
|   void set_arg(const FormatArgImpl* a) { arg_ = a; } | ||||
| 
 | ||||
|  private: | ||||
|   const FormatArgImpl* arg_; | ||||
| }; | ||||
| 
 | ||||
| // This is the type-erased class that the implementation uses.
 | ||||
| class UntypedFormatSpecImpl { | ||||
|  public: | ||||
|   UntypedFormatSpecImpl() = delete; | ||||
| 
 | ||||
|   explicit UntypedFormatSpecImpl(string_view s) : str_(s), pc_() {} | ||||
|   explicit UntypedFormatSpecImpl( | ||||
|       const str_format_internal::ParsedFormatBase* pc) | ||||
|       : pc_(pc) {} | ||||
|   string_view str() const { return str_; } | ||||
|   const str_format_internal::ParsedFormatBase* parsed_conversion() const { | ||||
|     return pc_; | ||||
|   } | ||||
| 
 | ||||
|   template <typename T> | ||||
|   static const UntypedFormatSpecImpl& Extract(const T& s) { | ||||
|     return s.spec_; | ||||
|   } | ||||
| 
 | ||||
|  private: | ||||
|   string_view str_; | ||||
|   const str_format_internal::ParsedFormatBase* pc_; | ||||
| }; | ||||
| 
 | ||||
| template <typename T, typename...> | ||||
| struct MakeDependent { | ||||
|   using type = T; | ||||
| }; | ||||
| 
 | ||||
| // Implicitly convertible from `const char*`, `string_view`, and the
 | ||||
| // `ExtendedParsedFormat` type. This abstraction allows all format functions to
 | ||||
| // operate on any without providing too many overloads.
 | ||||
| template <typename... Args> | ||||
| class FormatSpecTemplate | ||||
|     : public MakeDependent<UntypedFormatSpec, Args...>::type { | ||||
|   using Base = typename MakeDependent<UntypedFormatSpec, Args...>::type; | ||||
| 
 | ||||
|  public: | ||||
| #if ABSL_INTERNAL_ENABLE_FORMAT_CHECKER | ||||
| 
 | ||||
|   // Honeypot overload for when the std::string is not constexpr.
 | ||||
|   // We use the 'unavailable' attribute to give a better compiler error than
 | ||||
|   // just 'method is deleted'.
 | ||||
|   FormatSpecTemplate(...)  // NOLINT
 | ||||
|       __attribute__((unavailable("Format std::string is not constexpr."))); | ||||
| 
 | ||||
|   // Honeypot overload for when the format is constexpr and invalid.
 | ||||
|   // We use the 'unavailable' attribute to give a better compiler error than
 | ||||
|   // just 'method is deleted'.
 | ||||
|   // To avoid checking the format twice, we just check that the format is
 | ||||
|   // constexpr. If is it valid, then the overload below will kick in.
 | ||||
|   // We add the template here to make this overload have lower priority.
 | ||||
|   template <typename = void> | ||||
|   FormatSpecTemplate(const char* s)  // NOLINT
 | ||||
|       __attribute__(( | ||||
|           enable_if(str_format_internal::EnsureConstexpr(s), "constexpr trap"), | ||||
|           unavailable( | ||||
|               "Format specified does not match the arguments passed."))); | ||||
| 
 | ||||
|   template <typename T = void> | ||||
|   FormatSpecTemplate(string_view s)  // NOLINT
 | ||||
|       __attribute__((enable_if(str_format_internal::EnsureConstexpr(s), | ||||
|                                "constexpr trap"))) { | ||||
|     static_assert(sizeof(T*) == 0, | ||||
|                   "Format specified does not match the arguments passed."); | ||||
|   } | ||||
| 
 | ||||
|   // Good format overload.
 | ||||
|   FormatSpecTemplate(const char* s)  // NOLINT
 | ||||
|       __attribute__((enable_if(ValidFormatImpl<ArgumentToConv<Args>()...>(s), | ||||
|                                "bad format trap"))) | ||||
|       : Base(s) {} | ||||
| 
 | ||||
|   FormatSpecTemplate(string_view s)  // NOLINT
 | ||||
|       __attribute__((enable_if(ValidFormatImpl<ArgumentToConv<Args>()...>(s), | ||||
|                                "bad format trap"))) | ||||
|       : Base(s) {} | ||||
| 
 | ||||
| #else  // ABSL_INTERNAL_ENABLE_FORMAT_CHECKER
 | ||||
| 
 | ||||
|   FormatSpecTemplate(const char* s) : Base(s) {}  // NOLINT
 | ||||
|   FormatSpecTemplate(string_view s) : Base(s) {}  // NOLINT
 | ||||
| 
 | ||||
| #endif  // ABSL_INTERNAL_ENABLE_FORMAT_CHECKER
 | ||||
| 
 | ||||
|   template <Conv... C, typename = typename std::enable_if< | ||||
|                            sizeof...(C) == sizeof...(Args) && | ||||
|                            AllOf(Contains(ArgumentToConv<Args>(), | ||||
|                                           C)...)>::type> | ||||
|   FormatSpecTemplate(const ExtendedParsedFormat<C...>& pc)  // NOLINT
 | ||||
|       : Base(&pc) {} | ||||
| }; | ||||
| 
 | ||||
| template <typename... Args> | ||||
| struct FormatSpecDeductionBarrier { | ||||
|   using type = FormatSpecTemplate<Args...>; | ||||
| }; | ||||
| 
 | ||||
| class Streamable { | ||||
|  public: | ||||
|   Streamable(const UntypedFormatSpecImpl& format, | ||||
|              absl::Span<const FormatArgImpl> args) | ||||
|       : format_(format), args_(args.begin(), args.end()) {} | ||||
| 
 | ||||
|   std::ostream& Print(std::ostream& os) const; | ||||
| 
 | ||||
|   friend std::ostream& operator<<(std::ostream& os, const Streamable& l) { | ||||
|     return l.Print(os); | ||||
|   } | ||||
| 
 | ||||
|  private: | ||||
|   const UntypedFormatSpecImpl& format_; | ||||
|   absl::InlinedVector<FormatArgImpl, 4> args_; | ||||
| }; | ||||
| 
 | ||||
| // for testing
 | ||||
| std::string Summarize(const UntypedFormatSpecImpl& format, | ||||
|                  absl::Span<const FormatArgImpl> args); | ||||
| bool BindWithPack(const UnboundConversion* props, | ||||
|                   absl::Span<const FormatArgImpl> pack, BoundConversion* bound); | ||||
| 
 | ||||
| bool FormatUntyped(FormatRawSinkImpl raw_sink, | ||||
|                    const UntypedFormatSpecImpl& format, | ||||
|                    absl::Span<const FormatArgImpl> args); | ||||
| 
 | ||||
| std::string& AppendPack(std::string* out, const UntypedFormatSpecImpl& format, | ||||
|                    absl::Span<const FormatArgImpl> args); | ||||
| 
 | ||||
| inline std::string FormatPack(const UntypedFormatSpecImpl& format, | ||||
|                          absl::Span<const FormatArgImpl> args) { | ||||
|   std::string out; | ||||
|   AppendPack(&out, format, args); | ||||
|   return out; | ||||
| } | ||||
| 
 | ||||
| int FprintF(std::FILE* output, const UntypedFormatSpecImpl& format, | ||||
|             absl::Span<const FormatArgImpl> args); | ||||
| int SnprintF(char* output, size_t size, const UntypedFormatSpecImpl& format, | ||||
|              absl::Span<const FormatArgImpl> args); | ||||
| 
 | ||||
| // Returned by Streamed(v). Converts via '%s' to the std::string created
 | ||||
| // by std::ostream << v.
 | ||||
| template <typename T> | ||||
| class StreamedWrapper { | ||||
|  public: | ||||
|   explicit StreamedWrapper(const T& v) : v_(v) { } | ||||
| 
 | ||||
|  private: | ||||
|   template <typename S> | ||||
|   friend ConvertResult<Conv::s> FormatConvertImpl(const StreamedWrapper<S>& v, | ||||
|                                                   const ConversionSpec& conv, | ||||
|                                                   FormatSinkImpl* out); | ||||
|   const T& v_; | ||||
| }; | ||||
| 
 | ||||
| }  // namespace str_format_internal
 | ||||
| }  // namespace absl
 | ||||
| 
 | ||||
| #endif  // ABSL_STRINGS_INTERNAL_STR_FORMAT_BIND_H_
 | ||||
							
								
								
									
										131
									
								
								absl/strings/internal/str_format/bind_test.cc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								absl/strings/internal/str_format/bind_test.cc
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,131 @@ | |||
| #include "absl/strings/internal/str_format/bind.h" | ||||
| 
 | ||||
| #include <string.h> | ||||
| 
 | ||||
| #include "gtest/gtest.h" | ||||
| 
 | ||||
| namespace absl { | ||||
| namespace str_format_internal { | ||||
| namespace { | ||||
| 
 | ||||
| template <typename T, size_t N> | ||||
| size_t ArraySize(T (&)[N]) { | ||||
|   return N; | ||||
| } | ||||
| 
 | ||||
| class FormatBindTest : public ::testing::Test { | ||||
|  public: | ||||
|   bool Extract(const char *s, UnboundConversion *props, int *next) const { | ||||
|     absl::string_view src = s; | ||||
|     return ConsumeUnboundConversion(&src, props, next) && src.empty(); | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| TEST_F(FormatBindTest, BindSingle) { | ||||
|   struct Expectation { | ||||
|     int line; | ||||
|     const char *fmt; | ||||
|     int ok_phases; | ||||
|     const FormatArgImpl *arg; | ||||
|     int width; | ||||
|     int precision; | ||||
|     int next_arg; | ||||
|   }; | ||||
|   const int no = -1; | ||||
|   const int ia[] = { 10, 20, 30, 40}; | ||||
|   const FormatArgImpl args[] = {FormatArgImpl(ia[0]), FormatArgImpl(ia[1]), | ||||
|                                 FormatArgImpl(ia[2]), FormatArgImpl(ia[3])}; | ||||
| #pragma GCC diagnostic push | ||||
| #pragma GCC diagnostic ignored "-Wmissing-field-initializers" | ||||
|   const Expectation kExpect[] = { | ||||
|     {__LINE__, "d",          2, &args[0], no, no, 2}, | ||||
|     {__LINE__, "4d",         2, &args[0],  4, no, 2}, | ||||
|     {__LINE__, ".5d",        2, &args[0], no,  5, 2}, | ||||
|     {__LINE__, "4.5d",       2, &args[0],  4,  5, 2}, | ||||
|     {__LINE__, "*d",         2, &args[1], 10, no, 3}, | ||||
|     {__LINE__, ".*d",        2, &args[1], no, 10, 3}, | ||||
|     {__LINE__, "*.*d",       2, &args[2], 10, 20, 4}, | ||||
|     {__LINE__, "1$d",        2, &args[0], no, no, 0}, | ||||
|     {__LINE__, "2$d",        2, &args[1], no, no, 0}, | ||||
|     {__LINE__, "3$d",        2, &args[2], no, no, 0}, | ||||
|     {__LINE__, "4$d",        2, &args[3], no, no, 0}, | ||||
|     {__LINE__, "2$*1$d",     2, &args[1], 10, no, 0}, | ||||
|     {__LINE__, "2$*2$d",     2, &args[1], 20, no, 0}, | ||||
|     {__LINE__, "2$*3$d",     2, &args[1], 30, no, 0}, | ||||
|     {__LINE__, "2$.*1$d",    2, &args[1], no, 10, 0}, | ||||
|     {__LINE__, "2$.*2$d",    2, &args[1], no, 20, 0}, | ||||
|     {__LINE__, "2$.*3$d",    2, &args[1], no, 30, 0}, | ||||
|     {__LINE__, "2$*3$.*1$d", 2, &args[1], 30, 10, 0}, | ||||
|     {__LINE__, "2$*2$.*2$d", 2, &args[1], 20, 20, 0}, | ||||
|     {__LINE__, "2$*1$.*3$d", 2, &args[1], 10, 30, 0}, | ||||
|     {__LINE__, "2$*3$.*1$d", 2, &args[1], 30, 10, 0}, | ||||
|     {__LINE__, "1$*d",       0},  // indexed, then positional
 | ||||
|     {__LINE__, "*2$d",       0},  // positional, then indexed
 | ||||
|     {__LINE__, "6$d",        1},  // arg position out of bounds
 | ||||
|     {__LINE__, "1$6$d",      0},  // width position incorrectly specified
 | ||||
|     {__LINE__, "1$.6$d",     0},  // precision position incorrectly specified
 | ||||
|     {__LINE__, "1$*6$d",     1},  // width position out of bounds
 | ||||
|     {__LINE__, "1$.*6$d",    1},  // precision position out of bounds
 | ||||
|   }; | ||||
| #pragma GCC diagnostic pop | ||||
|   for (const Expectation &e : kExpect) { | ||||
|     SCOPED_TRACE(e.line); | ||||
|     SCOPED_TRACE(e.fmt); | ||||
|     UnboundConversion props; | ||||
|     BoundConversion bound; | ||||
|     int ok_phases = 0; | ||||
|     int next = 0; | ||||
|     if (Extract(e.fmt, &props, &next)) { | ||||
|       ++ok_phases; | ||||
|       if (BindWithPack(&props, args, &bound)) { | ||||
|         ++ok_phases; | ||||
|       } | ||||
|     } | ||||
|     EXPECT_EQ(e.ok_phases, ok_phases); | ||||
|     if (e.ok_phases < 2) continue; | ||||
|     if (e.arg != nullptr) { | ||||
|       EXPECT_EQ(e.arg, bound.arg()); | ||||
|     } | ||||
|     EXPECT_EQ(e.width, bound.width()); | ||||
|     EXPECT_EQ(e.precision, bound.precision()); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| TEST_F(FormatBindTest, FormatPack) { | ||||
|   struct Expectation { | ||||
|     int line; | ||||
|     const char *fmt; | ||||
|     const char *summary; | ||||
|   }; | ||||
|   const int ia[] = { 10, 20, 30, 40, -10 }; | ||||
|   const FormatArgImpl args[] = {FormatArgImpl(ia[0]), FormatArgImpl(ia[1]), | ||||
|                                 FormatArgImpl(ia[2]), FormatArgImpl(ia[3]), | ||||
|                                 FormatArgImpl(ia[4])}; | ||||
|   const Expectation kExpect[] = { | ||||
|     {__LINE__, "a%4db%dc", "a{10:4d}b{20:d}c"}, | ||||
|     {__LINE__, "a%.4db%dc", "a{10:.4d}b{20:d}c"}, | ||||
|     {__LINE__, "a%4.5db%dc", "a{10:4.5d}b{20:d}c"}, | ||||
|     {__LINE__, "a%db%4.5dc", "a{10:d}b{20:4.5d}c"}, | ||||
|     {__LINE__, "a%db%*.*dc", "a{10:d}b{40:20.30d}c"}, | ||||
|     {__LINE__, "a%.*fb", "a{20:.10f}b"}, | ||||
|     {__LINE__, "a%1$db%2$*3$.*4$dc", "a{10:d}b{20:30.40d}c"}, | ||||
|     {__LINE__, "a%4$db%3$*2$.*1$dc", "a{40:d}b{30:20.10d}c"}, | ||||
|     {__LINE__, "a%04ldb", "a{10:04ld}b"}, | ||||
|     {__LINE__, "a%-#04lldb", "a{10:-#04lld}b"}, | ||||
|     {__LINE__, "a%1$*5$db", "a{10:-10d}b"}, | ||||
|     {__LINE__, "a%1$.*5$db", "a{10:d}b"}, | ||||
|   }; | ||||
|   for (const Expectation &e : kExpect) { | ||||
|     absl::string_view fmt = e.fmt; | ||||
|     SCOPED_TRACE(e.line); | ||||
|     SCOPED_TRACE(e.fmt); | ||||
|     UntypedFormatSpecImpl format(fmt); | ||||
|     EXPECT_EQ(e.summary, | ||||
|               str_format_internal::Summarize(format, absl::MakeSpan(args))) | ||||
|         << "line:" << e.line; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| }  // namespace
 | ||||
| }  // namespace str_format_internal
 | ||||
| }  // namespace absl
 | ||||
							
								
								
									
										325
									
								
								absl/strings/internal/str_format/checker.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										325
									
								
								absl/strings/internal/str_format/checker.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,325 @@ | |||
| #ifndef ABSL_STRINGS_INTERNAL_STR_FORMAT_CHECKER_H_ | ||||
| #define ABSL_STRINGS_INTERNAL_STR_FORMAT_CHECKER_H_ | ||||
| 
 | ||||
| #include "absl/strings/internal/str_format/arg.h" | ||||
| #include "absl/strings/internal/str_format/extension.h" | ||||
| 
 | ||||
| // Compile time check support for entry points.
 | ||||
| 
 | ||||
| #ifndef ABSL_INTERNAL_ENABLE_FORMAT_CHECKER | ||||
| #if defined(__clang__) && !defined(__native_client__) | ||||
| #if __has_attribute(enable_if) | ||||
| #define ABSL_INTERNAL_ENABLE_FORMAT_CHECKER 1 | ||||
| #endif  // __has_attribute(enable_if)
 | ||||
| #endif  // defined(__clang__) && !defined(__native_client__)
 | ||||
| #endif  // ABSL_INTERNAL_ENABLE_FORMAT_CHECKER
 | ||||
| 
 | ||||
| namespace absl { | ||||
| namespace str_format_internal { | ||||
| 
 | ||||
| constexpr bool AllOf() { return true; } | ||||
| 
 | ||||
| template <typename... T> | ||||
| constexpr bool AllOf(bool b, T... t) { | ||||
|   return b && AllOf(t...); | ||||
| } | ||||
| 
 | ||||
| template <typename Arg> | ||||
| constexpr Conv ArgumentToConv() { | ||||
|   return decltype(str_format_internal::FormatConvertImpl( | ||||
|       std::declval<const Arg&>(), std::declval<const ConversionSpec&>(), | ||||
|       std::declval<FormatSinkImpl*>()))::kConv; | ||||
| } | ||||
| 
 | ||||
| #if 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 Conv* array; | ||||
|   int count; | ||||
| 
 | ||||
|   // We do the bound check here to avoid having to do it on the callers.
 | ||||
|   // Returning an empty Conv has the same effect as short circuiting because it
 | ||||
|   // will never match any conversion.
 | ||||
|   constexpr Conv operator[](int i) const { | ||||
|     return i < count ? array[i] : Conv{}; | ||||
|   } | ||||
| 
 | ||||
|   constexpr ConvList without_front() const { | ||||
|     return count != 0 ? ConvList{array + 1, count - 1} : *this; | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| template <size_t count> | ||||
| struct ConvListT { | ||||
|   // Make sure the array has size > 0.
 | ||||
|   Conv 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) { | ||||
|   return ContainsChar(chars, GetChar(format, 0)) | ||||
|              ? ConsumeAnyOf(ConsumeFront(format), chars) | ||||
|              : 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 { | ||||
|     return GetChar(format, 0) == '$' ? Integer{ConsumeFront(format), value} | ||||
|                                      : Integer{format, 0}; | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| constexpr Integer ParseDigits(string_view format, int value = 0) { | ||||
|   return IsDigit(GetChar(format, 0)) | ||||
|              ? ParseDigits(ConsumeFront(format), | ||||
|                            10 * value + GetChar(format, 0) - '0') | ||||
|              : 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 { | ||||
|     return IsDigit(GetChar(format_, 0)) | ||||
|                ? SetFormat(ParseDigits(format_).format) | ||||
|                : GetChar(format_, 0) == '*' | ||||
|                      ? is_positional_ | ||||
|                            ? VerifyPositional( | ||||
|                                  ParsePositional(ConsumeFront(format_)), '*') | ||||
|                            : SetFormat(ConsumeFront(format_)) | ||||
|                                  .ConsumeNextArg('*') | ||||
|                      : *this; | ||||
|   } | ||||
| 
 | ||||
|   // Consume the precision.
 | ||||
|   // If it is '*', we verify that it matches `args_`. `error_` is set if it
 | ||||
|   // doesn't match.
 | ||||
|   constexpr ConvParser ParsePrecision() const { | ||||
|     return GetChar(format_, 0) != '.' | ||||
|                ? *this | ||||
|                : GetChar(format_, 1) == '*' | ||||
|                      ? is_positional_ | ||||
|                            ? VerifyPositional( | ||||
|                                  ParsePositional(ConsumeFront(format_, 2)), '*') | ||||
|                            : SetFormat(ConsumeFront(format_, 2)) | ||||
|                                  .ConsumeNextArg('*') | ||||
|                      : SetFormat(ParseDigits(ConsumeFront(format_)).format); | ||||
|   } | ||||
| 
 | ||||
|   // 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 { | ||||
|     return is_positional_ | ||||
|                ? VerifyPositional({ConsumeFront(format_), arg_position_}, | ||||
|                                   GetChar(format_, 0)) | ||||
|                : ConsumeNextArg(GetChar(format_, 0)) | ||||
|                      .SetFormat(ConsumeFront(format_)); | ||||
|   } | ||||
| 
 | ||||
|   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 { | ||||
|     return (is_positional_ ? ParseArgPosition(ParsePositional(format_)) : *this) | ||||
|         .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) { | ||||
|     return FoundPercent(format) || !limit | ||||
|                ? format | ||||
|                : ConsumeNonPercentInner( | ||||
|                      ConsumeFront(format, GetChar(format, 0) == '%' && | ||||
|                                                   GetChar(format, 1) == '%' | ||||
|                                               ? 2 | ||||
|                                               : 1), | ||||
|                      limit - 1); | ||||
|   } | ||||
| 
 | ||||
|   // Consume characters until the next conversion spec %.
 | ||||
|   // It skips %%.
 | ||||
|   static constexpr string_view ConsumeNonPercent(string_view format) { | ||||
|     return FoundPercent(format) | ||||
|                ? format | ||||
|                : ConsumeNonPercent(ConsumeNonPercentInner(format)); | ||||
|   } | ||||
| 
 | ||||
|   static constexpr bool IsPositional(string_view format) { | ||||
|     return IsDigit(GetChar(format, 0)) ? IsPositional(ConsumeFront(format)) | ||||
|                                        : 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 <Conv... C> | ||||
| constexpr bool ValidFormatImpl(string_view format) { | ||||
|   return FormatParser(format, | ||||
|                       {ConvListT<sizeof...(C)>{{C...}}.list, sizeof...(C)}) | ||||
|       .Run(); | ||||
| } | ||||
| 
 | ||||
| #endif  // ABSL_INTERNAL_ENABLE_FORMAT_CHECKER
 | ||||
| 
 | ||||
| }  // namespace str_format_internal
 | ||||
| }  // namespace absl
 | ||||
| 
 | ||||
| #endif  // ABSL_STRINGS_INTERNAL_STR_FORMAT_CHECKER_H_
 | ||||
							
								
								
									
										150
									
								
								absl/strings/internal/str_format/checker_test.cc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								absl/strings/internal/str_format/checker_test.cc
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,150 @@ | |||
| #include <string> | ||||
| 
 | ||||
| #include "gmock/gmock.h" | ||||
| #include "gtest/gtest.h" | ||||
| #include "absl/strings/str_format.h" | ||||
| 
 | ||||
| namespace absl { | ||||
| namespace str_format_internal { | ||||
| namespace { | ||||
| 
 | ||||
| std::string ConvToString(Conv conv) { | ||||
|   std::string out; | ||||
| #define CONV_SET_CASE(c) \ | ||||
|   if (Contains(conv, Conv::c)) out += #c; | ||||
|   ABSL_CONVERSION_CHARS_EXPAND_(CONV_SET_CASE, ) | ||||
| #undef CONV_SET_CASE | ||||
|   if (Contains(conv, Conv::star)) out += "*"; | ||||
|   return out; | ||||
| } | ||||
| 
 | ||||
| TEST(StrFormatChecker, ArgumentToConv) { | ||||
|   Conv conv = ArgumentToConv<std::string>(); | ||||
|   EXPECT_EQ(ConvToString(conv), "s"); | ||||
| 
 | ||||
|   conv = ArgumentToConv<const char*>(); | ||||
|   EXPECT_EQ(ConvToString(conv), "sp"); | ||||
| 
 | ||||
|   conv = ArgumentToConv<double>(); | ||||
|   EXPECT_EQ(ConvToString(conv), "fFeEgGaA"); | ||||
| 
 | ||||
|   conv = ArgumentToConv<int>(); | ||||
|   EXPECT_EQ(ConvToString(conv), "cdiouxXfFeEgGaA*"); | ||||
| 
 | ||||
|   conv = ArgumentToConv<std::string*>(); | ||||
|   EXPECT_EQ(ConvToString(conv), "p"); | ||||
| } | ||||
| 
 | ||||
| #if ABSL_INTERNAL_ENABLE_FORMAT_CHECKER | ||||
| 
 | ||||
| struct Case { | ||||
|   bool result; | ||||
|   const char* format; | ||||
| }; | ||||
| 
 | ||||
| template <typename... Args> | ||||
| constexpr Case ValidFormat(const char* format) { | ||||
|   return {ValidFormatImpl<ArgumentToConv<Args>()...>(format), format}; | ||||
| } | ||||
| 
 | ||||
| TEST(StrFormatChecker, ValidFormat) { | ||||
|   // We want to make sure these expressions are constexpr and they have the
 | ||||
|   // expected value.
 | ||||
|   // If they are not constexpr the attribute will just ignore them and not give
 | ||||
|   // a compile time error.
 | ||||
|   enum e {}; | ||||
|   enum class e2 {}; | ||||
|   constexpr Case trues[] = { | ||||
|       ValidFormat<>("abc"),  //
 | ||||
| 
 | ||||
|       ValidFormat<e>("%d"),                             //
 | ||||
|       ValidFormat<e2>("%d"),                            //
 | ||||
|       ValidFormat<int>("%% %d"),                        //
 | ||||
|       ValidFormat<int>("%ld"),                          //
 | ||||
|       ValidFormat<int>("%lld"),                         //
 | ||||
|       ValidFormat<std::string>("%s"),                        //
 | ||||
|       ValidFormat<std::string>("%10s"),                      //
 | ||||
|       ValidFormat<int>("%.10x"),                        //
 | ||||
|       ValidFormat<int, int>("%*.3x"),                   //
 | ||||
|       ValidFormat<int>("%1.d"),                         //
 | ||||
|       ValidFormat<int>("%.d"),                          //
 | ||||
|       ValidFormat<int, double>("%d %g"),                //
 | ||||
|       ValidFormat<int, std::string>("%*s"),                  //
 | ||||
|       ValidFormat<int, double>("%.*f"),                 //
 | ||||
|       ValidFormat<void (*)(), volatile int*>("%p %p"),  //
 | ||||
|       ValidFormat<string_view, const char*, double, void*>( | ||||
|           "string_view=%s const char*=%s double=%f void*=%p)"), | ||||
| 
 | ||||
|       ValidFormat<int>("%% %1$d"),            //
 | ||||
|       ValidFormat<int>("%1$ld"),              //
 | ||||
|       ValidFormat<int>("%1$lld"),             //
 | ||||
|       ValidFormat<std::string>("%1$s"),            //
 | ||||
|       ValidFormat<std::string>("%1$10s"),          //
 | ||||
|       ValidFormat<int>("%1$.10x"),            //
 | ||||
|       ValidFormat<int>("%1$*1$.*1$d"),        //
 | ||||
|       ValidFormat<int, int>("%1$*2$.3x"),     //
 | ||||
|       ValidFormat<int>("%1$1.d"),             //
 | ||||
|       ValidFormat<int>("%1$.d"),              //
 | ||||
|       ValidFormat<double, int>("%2$d %1$g"),  //
 | ||||
|       ValidFormat<int, std::string>("%2$*1$s"),    //
 | ||||
|       ValidFormat<int, double>("%2$.*1$f"),   //
 | ||||
|       ValidFormat<void*, string_view, const char*, double>( | ||||
|           "string_view=%2$s const char*=%3$s double=%4$f void*=%1$p " | ||||
|           "repeat=%3$s)")}; | ||||
| 
 | ||||
|   for (Case c : trues) { | ||||
|     EXPECT_TRUE(c.result) << c.format; | ||||
|   } | ||||
| 
 | ||||
|   constexpr Case falses[] = { | ||||
|       ValidFormat<int>(""),  //
 | ||||
| 
 | ||||
|       ValidFormat<e>("%s"),             //
 | ||||
|       ValidFormat<e2>("%s"),            //
 | ||||
|       ValidFormat<>("%s"),              //
 | ||||
|       ValidFormat<>("%r"),              //
 | ||||
|       ValidFormat<int>("%s"),           //
 | ||||
|       ValidFormat<int>("%.1.d"),        //
 | ||||
|       ValidFormat<int>("%*1d"),         //
 | ||||
|       ValidFormat<int>("%1-d"),         //
 | ||||
|       ValidFormat<std::string, int>("%*s"),  //
 | ||||
|       ValidFormat<int>("%*d"),          //
 | ||||
|       ValidFormat<std::string>("%p"),        //
 | ||||
|       ValidFormat<int (*)(int)>("%d"),  //
 | ||||
| 
 | ||||
|       ValidFormat<>("%3$d"),                //
 | ||||
|       ValidFormat<>("%1$r"),                //
 | ||||
|       ValidFormat<int>("%1$s"),             //
 | ||||
|       ValidFormat<int>("%1$.1.d"),          //
 | ||||
|       ValidFormat<int>("%1$*2$1d"),         //
 | ||||
|       ValidFormat<int>("%1$1-d"),           //
 | ||||
|       ValidFormat<std::string, int>("%2$*1$s"),  //
 | ||||
|       ValidFormat<std::string>("%1$p"), | ||||
| 
 | ||||
|       ValidFormat<int, int>("%d %2$d"),  //
 | ||||
|   }; | ||||
| 
 | ||||
|   for (Case c : falses) { | ||||
|     EXPECT_FALSE(c.result) << c.format; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| TEST(StrFormatChecker, LongFormat) { | ||||
| #define CHARS_X_40 "1234567890123456789012345678901234567890" | ||||
| #define CHARS_X_400                                                            \ | ||||
|   CHARS_X_40 CHARS_X_40 CHARS_X_40 CHARS_X_40 CHARS_X_40 CHARS_X_40 CHARS_X_40 \ | ||||
|       CHARS_X_40 CHARS_X_40 CHARS_X_40 | ||||
| #define CHARS_X_4000                                                      \ | ||||
|   CHARS_X_400 CHARS_X_400 CHARS_X_400 CHARS_X_400 CHARS_X_400 CHARS_X_400 \ | ||||
|       CHARS_X_400 CHARS_X_400 CHARS_X_400 CHARS_X_400 | ||||
|   constexpr char long_format[] = | ||||
|       CHARS_X_4000 "%d" CHARS_X_4000 "%s" CHARS_X_4000; | ||||
|   constexpr bool is_valid = ValidFormat<int, std::string>(long_format).result; | ||||
|   EXPECT_TRUE(is_valid); | ||||
| } | ||||
| 
 | ||||
| #endif  // ABSL_INTERNAL_ENABLE_FORMAT_CHECKER
 | ||||
| 
 | ||||
| }  // namespace
 | ||||
| }  // namespace str_format_internal
 | ||||
| }  // namespace absl
 | ||||
							
								
								
									
										575
									
								
								absl/strings/internal/str_format/convert_test.cc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										575
									
								
								absl/strings/internal/str_format/convert_test.cc
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,575 @@ | |||
| #include <errno.h> | ||||
| #include <stdarg.h> | ||||
| #include <stdio.h> | ||||
| #include <cmath> | ||||
| #include <string> | ||||
| 
 | ||||
| #include "gtest/gtest.h" | ||||
| #include "absl/strings/internal/str_format/bind.h" | ||||
| 
 | ||||
| namespace absl { | ||||
| namespace str_format_internal { | ||||
| namespace { | ||||
| 
 | ||||
| template <typename T, size_t N> | ||||
| size_t ArraySize(T (&)[N]) { | ||||
|   return N; | ||||
| } | ||||
| 
 | ||||
| std::string LengthModFor(float) { return ""; } | ||||
| std::string LengthModFor(double) { return ""; } | ||||
| std::string LengthModFor(long double) { return "L"; } | ||||
| std::string LengthModFor(char) { return "hh"; } | ||||
| std::string LengthModFor(signed char) { return "hh"; } | ||||
| std::string LengthModFor(unsigned char) { return "hh"; } | ||||
| std::string LengthModFor(short) { return "h"; }           // NOLINT
 | ||||
| std::string LengthModFor(unsigned short) { return "h"; }  // NOLINT
 | ||||
| std::string LengthModFor(int) { return ""; } | ||||
| std::string LengthModFor(unsigned) { return ""; } | ||||
| std::string LengthModFor(long) { return "l"; }                 // NOLINT
 | ||||
| std::string LengthModFor(unsigned long) { return "l"; }        // NOLINT
 | ||||
| std::string LengthModFor(long long) { return "ll"; }           // NOLINT
 | ||||
| std::string LengthModFor(unsigned long long) { return "ll"; }  // NOLINT
 | ||||
| 
 | ||||
| std::string EscCharImpl(int v) { | ||||
|   if (isprint(v)) return std::string(1, static_cast<char>(v)); | ||||
|   char buf[64]; | ||||
|   int n = snprintf(buf, sizeof(buf), "\\%#.2x", | ||||
|                    static_cast<unsigned>(v & 0xff)); | ||||
|   assert(n > 0 && n < sizeof(buf)); | ||||
|   return std::string(buf, n); | ||||
| } | ||||
| 
 | ||||
| std::string Esc(char v) { return EscCharImpl(v); } | ||||
| std::string Esc(signed char v) { return EscCharImpl(v); } | ||||
| std::string Esc(unsigned char v) { return EscCharImpl(v); } | ||||
| 
 | ||||
| template <typename T> | ||||
| std::string Esc(const T &v) { | ||||
|   std::ostringstream oss; | ||||
|   oss << v; | ||||
|   return oss.str(); | ||||
| } | ||||
| 
 | ||||
| void StrAppend(std::string *dst, const char *format, va_list ap) { | ||||
|   // First try with a small fixed size buffer
 | ||||
|   static const int kSpaceLength = 1024; | ||||
|   char space[kSpaceLength]; | ||||
| 
 | ||||
|   // It's possible for methods that use a va_list to invalidate
 | ||||
|   // the data in it upon use.  The fix is to make a copy
 | ||||
|   // of the structure before using it and use that copy instead.
 | ||||
|   va_list backup_ap; | ||||
|   va_copy(backup_ap, ap); | ||||
|   int result = vsnprintf(space, kSpaceLength, format, backup_ap); | ||||
|   va_end(backup_ap); | ||||
|   if (result < kSpaceLength) { | ||||
|     if (result >= 0) { | ||||
|       // Normal case -- everything fit.
 | ||||
|       dst->append(space, result); | ||||
|       return; | ||||
|     } | ||||
|     if (result < 0) { | ||||
|       // Just an error.
 | ||||
|       return; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   // Increase the buffer size to the size requested by vsnprintf,
 | ||||
|   // plus one for the closing \0.
 | ||||
|   int length = result + 1; | ||||
|   char *buf = new char[length]; | ||||
| 
 | ||||
|   // Restore the va_list before we use it again
 | ||||
|   va_copy(backup_ap, ap); | ||||
|   result = vsnprintf(buf, length, format, backup_ap); | ||||
|   va_end(backup_ap); | ||||
| 
 | ||||
|   if (result >= 0 && result < length) { | ||||
|     // It fit
 | ||||
|     dst->append(buf, result); | ||||
|   } | ||||
|   delete[] buf; | ||||
| } | ||||
| 
 | ||||
| std::string StrPrint(const char *format, ...) { | ||||
|   va_list ap; | ||||
|   va_start(ap, format); | ||||
|   std::string result; | ||||
|   StrAppend(&result, format, ap); | ||||
|   va_end(ap); | ||||
|   return result; | ||||
| } | ||||
| 
 | ||||
| class FormatConvertTest : public ::testing::Test { }; | ||||
| 
 | ||||
| template <typename T> | ||||
| void TestStringConvert(const T& str) { | ||||
|   const FormatArgImpl args[] = {FormatArgImpl(str)}; | ||||
|   struct Expectation { | ||||
|     const char *out; | ||||
|     const char *fmt; | ||||
|   }; | ||||
|   const Expectation kExpect[] = { | ||||
|     {"hello",  "%1$s"      }, | ||||
|     {"",       "%1$.s"     }, | ||||
|     {"",       "%1$.0s"    }, | ||||
|     {"h",      "%1$.1s"    }, | ||||
|     {"he",     "%1$.2s"    }, | ||||
|     {"hello",  "%1$.10s"   }, | ||||
|     {" hello", "%1$6s"     }, | ||||
|     {"   he",  "%1$5.2s"   }, | ||||
|     {"he   ",  "%1$-5.2s"  }, | ||||
|     {"hello ", "%1$-6.10s" }, | ||||
|   }; | ||||
|   for (const Expectation &e : kExpect) { | ||||
|     UntypedFormatSpecImpl format(e.fmt); | ||||
|     EXPECT_EQ(e.out, FormatPack(format, absl::MakeSpan(args))); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| TEST_F(FormatConvertTest, BasicString) { | ||||
|   TestStringConvert("hello");  // As char array.
 | ||||
|   TestStringConvert(static_cast<const char*>("hello")); | ||||
|   TestStringConvert(std::string("hello")); | ||||
|   TestStringConvert(string_view("hello")); | ||||
| } | ||||
| 
 | ||||
| TEST_F(FormatConvertTest, NullString) { | ||||
|   const char* p = nullptr; | ||||
|   UntypedFormatSpecImpl format("%s"); | ||||
|   EXPECT_EQ("", FormatPack(format, {FormatArgImpl(p)})); | ||||
| } | ||||
| 
 | ||||
| TEST_F(FormatConvertTest, StringPrecision) { | ||||
|   // We cap at the precision.
 | ||||
|   char c = 'a'; | ||||
|   const char* p = &c; | ||||
|   UntypedFormatSpecImpl format("%.1s"); | ||||
|   EXPECT_EQ("a", FormatPack(format, {FormatArgImpl(p)})); | ||||
| 
 | ||||
|   // We cap at the nul terminator.
 | ||||
|   p = "ABC"; | ||||
|   UntypedFormatSpecImpl format2("%.10s"); | ||||
|   EXPECT_EQ("ABC", FormatPack(format2, {FormatArgImpl(p)})); | ||||
| } | ||||
| 
 | ||||
| TEST_F(FormatConvertTest, Pointer) { | ||||
| #if _MSC_VER | ||||
|   // MSVC's printf implementation prints pointers differently. We can't easily
 | ||||
|   // compare our implementation to theirs.
 | ||||
|   return; | ||||
| #endif | ||||
|   static int x = 0; | ||||
|   const int *xp = &x; | ||||
|   char c = 'h'; | ||||
|   char *mcp = &c; | ||||
|   const char *cp = "hi"; | ||||
|   const char *cnil = nullptr; | ||||
|   const int *inil = nullptr; | ||||
|   using VoidF = void (*)(); | ||||
|   VoidF fp = [] {}, fnil = nullptr; | ||||
|   volatile char vc; | ||||
|   volatile char* vcp = &vc; | ||||
|   volatile char* vcnil = nullptr; | ||||
|   const FormatArgImpl args[] = { | ||||
|       FormatArgImpl(xp),   FormatArgImpl(cp),  FormatArgImpl(inil), | ||||
|       FormatArgImpl(cnil), FormatArgImpl(mcp), FormatArgImpl(fp), | ||||
|       FormatArgImpl(fnil), FormatArgImpl(vcp), FormatArgImpl(vcnil), | ||||
|   }; | ||||
|   struct Expectation { | ||||
|     std::string out; | ||||
|     const char *fmt; | ||||
|   }; | ||||
|   const Expectation kExpect[] = { | ||||
|       {StrPrint("%p", &x), "%p"}, | ||||
|       {StrPrint("%20p", &x), "%20p"}, | ||||
|       {StrPrint("%.1p", &x), "%.1p"}, | ||||
|       {StrPrint("%.20p", &x), "%.20p"}, | ||||
|       {StrPrint("%30.20p", &x), "%30.20p"}, | ||||
| 
 | ||||
|       {StrPrint("%-p", &x), "%-p"}, | ||||
|       {StrPrint("%-20p", &x), "%-20p"}, | ||||
|       {StrPrint("%-.1p", &x), "%-.1p"}, | ||||
|       {StrPrint("%.20p", &x), "%.20p"}, | ||||
|       {StrPrint("%-30.20p", &x), "%-30.20p"}, | ||||
| 
 | ||||
|       {StrPrint("%p", cp), "%2$p"},   // const char*
 | ||||
|       {"(nil)", "%3$p"},              // null const char *
 | ||||
|       {"(nil)", "%4$p"},              // null const int *
 | ||||
|       {StrPrint("%p", mcp), "%5$p"},  // nonconst char*
 | ||||
| 
 | ||||
|       {StrPrint("%p", fp), "%6$p"},   // function pointer
 | ||||
|       {StrPrint("%p", vcp), "%8$p"},  // function pointer
 | ||||
| 
 | ||||
| #ifndef __APPLE__ | ||||
|       // Apple's printf differs here (0x0 vs. nil)
 | ||||
|       {StrPrint("%p", fnil), "%7$p"},   // null function pointer
 | ||||
|       {StrPrint("%p", vcnil), "%9$p"},  // null function pointer
 | ||||
| #endif | ||||
|   }; | ||||
|   for (const Expectation &e : kExpect) { | ||||
|     UntypedFormatSpecImpl format(e.fmt); | ||||
|     EXPECT_EQ(e.out, FormatPack(format, absl::MakeSpan(args))) << e.fmt; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| struct Cardinal { | ||||
|   enum Pos { k1 = 1, k2 = 2, k3 = 3 }; | ||||
|   enum Neg { kM1 = -1, kM2 = -2, kM3 = -3 }; | ||||
| }; | ||||
| 
 | ||||
| TEST_F(FormatConvertTest, Enum) { | ||||
|   const Cardinal::Pos k3 = Cardinal::k3; | ||||
|   const Cardinal::Neg km3 = Cardinal::kM3; | ||||
|   const FormatArgImpl args[] = {FormatArgImpl(k3), FormatArgImpl(km3)}; | ||||
|   UntypedFormatSpecImpl format("%1$d"); | ||||
|   UntypedFormatSpecImpl format2("%2$d"); | ||||
|   EXPECT_EQ("3", FormatPack(format, absl::MakeSpan(args))); | ||||
|   EXPECT_EQ("-3", FormatPack(format2, absl::MakeSpan(args))); | ||||
| } | ||||
| 
 | ||||
| template <typename T> | ||||
| class TypedFormatConvertTest : public FormatConvertTest { }; | ||||
| 
 | ||||
| TYPED_TEST_CASE_P(TypedFormatConvertTest); | ||||
| 
 | ||||
| std::vector<std::string> AllFlagCombinations() { | ||||
|   const char kFlags[] = {'-', '#', '0', '+', ' '}; | ||||
|   std::vector<std::string> result; | ||||
|   for (size_t fsi = 0; fsi < (1ull << ArraySize(kFlags)); ++fsi) { | ||||
|     std::string flag_set; | ||||
|     for (size_t fi = 0; fi < ArraySize(kFlags); ++fi) | ||||
|       if (fsi & (1ull << fi)) | ||||
|         flag_set += kFlags[fi]; | ||||
|     result.push_back(flag_set); | ||||
|   } | ||||
|   return result; | ||||
| } | ||||
| 
 | ||||
| TYPED_TEST_P(TypedFormatConvertTest, AllIntsWithFlags) { | ||||
|   typedef TypeParam T; | ||||
|   typedef typename std::make_unsigned<T>::type UnsignedT; | ||||
|   using remove_volatile_t = typename std::remove_volatile<T>::type; | ||||
|   const T kMin = std::numeric_limits<remove_volatile_t>::min(); | ||||
|   const T kMax = std::numeric_limits<remove_volatile_t>::max(); | ||||
|   const T kVals[] = { | ||||
|       remove_volatile_t(1), | ||||
|       remove_volatile_t(2), | ||||
|       remove_volatile_t(3), | ||||
|       remove_volatile_t(123), | ||||
|       remove_volatile_t(-1), | ||||
|       remove_volatile_t(-2), | ||||
|       remove_volatile_t(-3), | ||||
|       remove_volatile_t(-123), | ||||
|       remove_volatile_t(0), | ||||
|       kMax - remove_volatile_t(1), | ||||
|       kMax, | ||||
|       kMin + remove_volatile_t(1), | ||||
|       kMin, | ||||
|   }; | ||||
|   const char kConvChars[] = {'d', 'i', 'u', 'o', 'x', 'X'}; | ||||
|   const std::string kWid[] = {"", "4", "10"}; | ||||
|   const std::string kPrec[] = {"", ".", ".0", ".4", ".10"}; | ||||
| 
 | ||||
|   const std::vector<std::string> flag_sets = AllFlagCombinations(); | ||||
| 
 | ||||
|   for (size_t vi = 0; vi < ArraySize(kVals); ++vi) { | ||||
|     const T val = kVals[vi]; | ||||
|     SCOPED_TRACE(Esc(val)); | ||||
|     const FormatArgImpl args[] = {FormatArgImpl(val)}; | ||||
|     for (size_t ci = 0; ci < ArraySize(kConvChars); ++ci) { | ||||
|       const char conv_char = kConvChars[ci]; | ||||
|       for (size_t fsi = 0; fsi < flag_sets.size(); ++fsi) { | ||||
|         const std::string &flag_set = flag_sets[fsi]; | ||||
|         for (size_t wi = 0; wi < ArraySize(kWid); ++wi) { | ||||
|           const std::string &wid = kWid[wi]; | ||||
|           for (size_t pi = 0; pi < ArraySize(kPrec); ++pi) { | ||||
|             const std::string &prec = kPrec[pi]; | ||||
| 
 | ||||
|             const bool is_signed_conv = (conv_char == 'd' || conv_char == 'i'); | ||||
|             const bool is_unsigned_to_signed = | ||||
|                 !std::is_signed<T>::value && is_signed_conv; | ||||
|             // Don't consider sign-related flags '+' and ' ' when doing
 | ||||
|             // unsigned to signed conversions.
 | ||||
|             if (is_unsigned_to_signed && | ||||
|                 flag_set.find_first_of("+ ") != std::string::npos) { | ||||
|               continue; | ||||
|             } | ||||
| 
 | ||||
|             std::string new_fmt("%"); | ||||
|             new_fmt += flag_set; | ||||
|             new_fmt += wid; | ||||
|             new_fmt += prec; | ||||
|             // old and new always agree up to here.
 | ||||
|             std::string old_fmt = new_fmt; | ||||
|             new_fmt += conv_char; | ||||
|             std::string old_result; | ||||
|             if (is_unsigned_to_signed) { | ||||
|               // don't expect agreement on unsigned formatted as signed,
 | ||||
|               // as printf can't do that conversion properly. For those
 | ||||
|               // cases, we do expect agreement with printf with a "%u"
 | ||||
|               // and the unsigned equivalent of 'val'.
 | ||||
|               UnsignedT uval = val; | ||||
|               old_fmt += LengthModFor(uval); | ||||
|               old_fmt += "u"; | ||||
|               old_result = StrPrint(old_fmt.c_str(), uval); | ||||
|             } else { | ||||
|               old_fmt += LengthModFor(val); | ||||
|               old_fmt += conv_char; | ||||
|               old_result = StrPrint(old_fmt.c_str(), val); | ||||
|             } | ||||
| 
 | ||||
|             SCOPED_TRACE(std::string() + " old_fmt: \"" + old_fmt + | ||||
|                          "\"'" | ||||
|                          " new_fmt: \"" + | ||||
|                          new_fmt + "\""); | ||||
|             UntypedFormatSpecImpl format(new_fmt); | ||||
|             EXPECT_EQ(old_result, FormatPack(format, absl::MakeSpan(args))); | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| TYPED_TEST_P(TypedFormatConvertTest, Char) { | ||||
|   typedef TypeParam T; | ||||
|   using remove_volatile_t = typename std::remove_volatile<T>::type; | ||||
|   static const T kMin = std::numeric_limits<remove_volatile_t>::min(); | ||||
|   static const T kMax = std::numeric_limits<remove_volatile_t>::max(); | ||||
|   T kVals[] = { | ||||
|     remove_volatile_t(1), remove_volatile_t(2), remove_volatile_t(10), | ||||
|     remove_volatile_t(-1), remove_volatile_t(-2), remove_volatile_t(-10), | ||||
|     remove_volatile_t(0), | ||||
|     kMin + remove_volatile_t(1), kMin, | ||||
|     kMax - remove_volatile_t(1), kMax | ||||
|   }; | ||||
|   for (const T &c : kVals) { | ||||
|     const FormatArgImpl args[] = {FormatArgImpl(c)}; | ||||
|     UntypedFormatSpecImpl format("%c"); | ||||
|     EXPECT_EQ(StrPrint("%c", c), FormatPack(format, absl::MakeSpan(args))); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| REGISTER_TYPED_TEST_CASE_P(TypedFormatConvertTest, AllIntsWithFlags, Char); | ||||
| 
 | ||||
| typedef ::testing::Types< | ||||
|     int, unsigned, volatile int, | ||||
|     short, unsigned short, | ||||
|     long, unsigned long, | ||||
|     long long, unsigned long long, | ||||
|     signed char, unsigned char, char> | ||||
|     AllIntTypes; | ||||
| INSTANTIATE_TYPED_TEST_CASE_P(TypedFormatConvertTestWithAllIntTypes, | ||||
|                               TypedFormatConvertTest, AllIntTypes); | ||||
| TEST_F(FormatConvertTest, Uint128) { | ||||
|   absl::uint128 v = static_cast<absl::uint128>(0x1234567890abcdef) * 1979; | ||||
|   absl::uint128 max = absl::Uint128Max(); | ||||
|   const FormatArgImpl args[] = {FormatArgImpl(v), FormatArgImpl(max)}; | ||||
| 
 | ||||
|   struct Case { | ||||
|     const char* format; | ||||
|     const char* expected; | ||||
|   } cases[] = { | ||||
|       {"%1$d", "2595989796776606496405"}, | ||||
|       {"%1$30d", "        2595989796776606496405"}, | ||||
|       {"%1$-30d", "2595989796776606496405        "}, | ||||
|       {"%1$u", "2595989796776606496405"}, | ||||
|       {"%1$x", "8cba9876066020f695"}, | ||||
|       {"%2$d", "340282366920938463463374607431768211455"}, | ||||
|       {"%2$u", "340282366920938463463374607431768211455"}, | ||||
|       {"%2$x", "ffffffffffffffffffffffffffffffff"}, | ||||
|   }; | ||||
| 
 | ||||
|   for (auto c : cases) { | ||||
|     UntypedFormatSpecImpl format(c.format); | ||||
|     EXPECT_EQ(c.expected, FormatPack(format, absl::MakeSpan(args))); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| TEST_F(FormatConvertTest, Float) { | ||||
| #if _MSC_VER | ||||
|   // MSVC has a different rounding policy than us so we can't test our
 | ||||
|   // implementation against the native one there.
 | ||||
|   return; | ||||
| #endif  // _MSC_VER
 | ||||
| 
 | ||||
|   const char *const kFormats[] = { | ||||
|       "%",  "%.3",  "%8.5",   "%9",   "%.60", "%.30",   "%03",    "%+", | ||||
|       "% ", "%-10", "%#15.3", "%#.0", "%.0",  "%1$*2$", "%1$.*2$"}; | ||||
| 
 | ||||
|   std::vector<double> doubles = {0.0, | ||||
|                                  -0.0, | ||||
|                                  .99999999999999, | ||||
|                                  99999999999999., | ||||
|                                  std::numeric_limits<double>::max(), | ||||
|                                  -std::numeric_limits<double>::max(), | ||||
|                                  std::numeric_limits<double>::min(), | ||||
|                                  -std::numeric_limits<double>::min(), | ||||
|                                  std::numeric_limits<double>::lowest(), | ||||
|                                  -std::numeric_limits<double>::lowest(), | ||||
|                                  std::numeric_limits<double>::epsilon(), | ||||
|                                  std::numeric_limits<double>::epsilon() + 1, | ||||
|                                  std::numeric_limits<double>::infinity(), | ||||
|                                  -std::numeric_limits<double>::infinity()}; | ||||
| 
 | ||||
| #ifndef __APPLE__ | ||||
|   // Apple formats NaN differently (+nan) vs. (nan)
 | ||||
|   doubles.push_back(std::nan("")); | ||||
| #endif | ||||
| 
 | ||||
|   // Some regression tests.
 | ||||
|   doubles.push_back(0.99999999999999989); | ||||
| 
 | ||||
|   if (std::numeric_limits<double>::has_denorm != std::denorm_absent) { | ||||
|     doubles.push_back(std::numeric_limits<double>::denorm_min()); | ||||
|     doubles.push_back(-std::numeric_limits<double>::denorm_min()); | ||||
|   } | ||||
| 
 | ||||
|   for (double base : | ||||
|        {1., 12., 123., 1234., 12345., 123456., 1234567., 12345678., 123456789., | ||||
|         1234567890., 12345678901., 123456789012., 1234567890123.}) { | ||||
|     for (int exp = -123; exp <= 123; ++exp) { | ||||
|       for (int sign : {1, -1}) { | ||||
|         doubles.push_back(sign * std::ldexp(base, exp)); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   for (const char *fmt : kFormats) { | ||||
|     for (char f : {'f', 'F',  //
 | ||||
|                    'g', 'G',  //
 | ||||
|                    'a', 'A',  //
 | ||||
|                    'e', 'E'}) { | ||||
|       std::string fmt_str = std::string(fmt) + f; | ||||
|       for (double d : doubles) { | ||||
|         int i = -10; | ||||
|         FormatArgImpl args[2] = {FormatArgImpl(d), FormatArgImpl(i)}; | ||||
|         UntypedFormatSpecImpl format(fmt_str); | ||||
|         // We use ASSERT_EQ here because failures are usually correlated and a
 | ||||
|         // bug would print way too many failed expectations causing the test to
 | ||||
|         // time out.
 | ||||
|         ASSERT_EQ(StrPrint(fmt_str.c_str(), d, i), | ||||
|                   FormatPack(format, absl::MakeSpan(args))) | ||||
|             << fmt_str << " " << StrPrint("%.18g", d) << " " | ||||
|             << StrPrint("%.999f", d); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| TEST_F(FormatConvertTest, LongDouble) { | ||||
|   const char *const kFormats[] = {"%",    "%.3", "%8.5", "%9", | ||||
|                                   "%.60", "%+",  "% ",   "%-10"}; | ||||
| 
 | ||||
|   // This value is not representable in double, but it is in long double that
 | ||||
|   // uses the extended format.
 | ||||
|   // This is to verify that we are not truncating the value mistakenly through a
 | ||||
|   // double.
 | ||||
|   long double very_precise = 10000000000000000.25L; | ||||
| 
 | ||||
|   std::vector<long double> doubles = { | ||||
|       0.0, | ||||
|       -0.0, | ||||
|       very_precise, | ||||
|       1 / very_precise, | ||||
|       std::numeric_limits<long double>::max(), | ||||
|       -std::numeric_limits<long double>::max(), | ||||
|       std::numeric_limits<long double>::min(), | ||||
|       -std::numeric_limits<long double>::min(), | ||||
|       std::numeric_limits<long double>::infinity(), | ||||
|       -std::numeric_limits<long double>::infinity()}; | ||||
| 
 | ||||
|   for (const char *fmt : kFormats) { | ||||
|     for (char f : {'f', 'F',  //
 | ||||
|                    'g', 'G',  //
 | ||||
|                    'a', 'A',  //
 | ||||
|                    'e', 'E'}) { | ||||
|       std::string fmt_str = std::string(fmt) + 'L' + f; | ||||
|       for (auto d : doubles) { | ||||
|         FormatArgImpl arg(d); | ||||
|         UntypedFormatSpecImpl format(fmt_str); | ||||
|         // We use ASSERT_EQ here because failures are usually correlated and a
 | ||||
|         // bug would print way too many failed expectations causing the test to
 | ||||
|         // time out.
 | ||||
|         ASSERT_EQ(StrPrint(fmt_str.c_str(), d), | ||||
|                   FormatPack(format, {&arg, 1})) | ||||
|             << fmt_str << " " << StrPrint("%.18Lg", d) << " " | ||||
|             << StrPrint("%.999Lf", d); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| TEST_F(FormatConvertTest, IntAsFloat) { | ||||
|   const int kMin = std::numeric_limits<int>::min(); | ||||
|   const int kMax = std::numeric_limits<int>::max(); | ||||
|   const int ia[] = { | ||||
|     1, 2, 3, 123, | ||||
|     -1, -2, -3, -123, | ||||
|     0, kMax - 1, kMax, kMin + 1, kMin }; | ||||
|   for (const int fx : ia) { | ||||
|     SCOPED_TRACE(fx); | ||||
|     const FormatArgImpl args[] = {FormatArgImpl(fx)}; | ||||
|     struct Expectation { | ||||
|       int line; | ||||
|       std::string out; | ||||
|       const char *fmt; | ||||
|     }; | ||||
|     const double dx = static_cast<double>(fx); | ||||
|     const Expectation kExpect[] = { | ||||
|       { __LINE__, StrPrint("%f", dx), "%f" }, | ||||
|       { __LINE__, StrPrint("%12f", dx), "%12f" }, | ||||
|       { __LINE__, StrPrint("%.12f", dx), "%.12f" }, | ||||
|       { __LINE__, StrPrint("%12a", dx), "%12a" }, | ||||
|       { __LINE__, StrPrint("%.12a", dx), "%.12a" }, | ||||
|     }; | ||||
|     for (const Expectation &e : kExpect) { | ||||
|       SCOPED_TRACE(e.line); | ||||
|       SCOPED_TRACE(e.fmt); | ||||
|       UntypedFormatSpecImpl format(e.fmt); | ||||
|       EXPECT_EQ(e.out, FormatPack(format, absl::MakeSpan(args))); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| template <typename T> | ||||
| bool FormatFails(const char* test_format, T value) { | ||||
|   std::string format_string = std::string("<<") + test_format + ">>"; | ||||
|   UntypedFormatSpecImpl format(format_string); | ||||
| 
 | ||||
|   int one = 1; | ||||
|   const FormatArgImpl args[] = {FormatArgImpl(value), FormatArgImpl(one)}; | ||||
|   EXPECT_EQ(FormatPack(format, absl::MakeSpan(args)), "") | ||||
|       << "format=" << test_format << " value=" << value; | ||||
|   return FormatPack(format, absl::MakeSpan(args)).empty(); | ||||
| } | ||||
| 
 | ||||
| TEST_F(FormatConvertTest, ExpectedFailures) { | ||||
|   // Int input
 | ||||
|   EXPECT_TRUE(FormatFails("%p", 1)); | ||||
|   EXPECT_TRUE(FormatFails("%s", 1)); | ||||
|   EXPECT_TRUE(FormatFails("%n", 1)); | ||||
| 
 | ||||
|   // Double input
 | ||||
|   EXPECT_TRUE(FormatFails("%p", 1.)); | ||||
|   EXPECT_TRUE(FormatFails("%s", 1.)); | ||||
|   EXPECT_TRUE(FormatFails("%n", 1.)); | ||||
|   EXPECT_TRUE(FormatFails("%c", 1.)); | ||||
|   EXPECT_TRUE(FormatFails("%d", 1.)); | ||||
|   EXPECT_TRUE(FormatFails("%x", 1.)); | ||||
|   EXPECT_TRUE(FormatFails("%*d", 1.)); | ||||
| 
 | ||||
|   // String input
 | ||||
|   EXPECT_TRUE(FormatFails("%n", "")); | ||||
|   EXPECT_TRUE(FormatFails("%c", "")); | ||||
|   EXPECT_TRUE(FormatFails("%d", "")); | ||||
|   EXPECT_TRUE(FormatFails("%x", "")); | ||||
|   EXPECT_TRUE(FormatFails("%f", "")); | ||||
|   EXPECT_TRUE(FormatFails("%*d", "")); | ||||
| } | ||||
| 
 | ||||
| }  // namespace
 | ||||
| }  // namespace str_format_internal
 | ||||
| }  // namespace absl
 | ||||
							
								
								
									
										84
									
								
								absl/strings/internal/str_format/extension.cc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								absl/strings/internal/str_format/extension.cc
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,84 @@ | |||
| //
 | ||||
| // Copyright 2017 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
 | ||||
| //
 | ||||
| //      http://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.
 | ||||
| 
 | ||||
| #include "absl/strings/internal/str_format/extension.h" | ||||
| 
 | ||||
| #include <errno.h> | ||||
| #include <algorithm> | ||||
| #include <string> | ||||
| 
 | ||||
| namespace absl { | ||||
| namespace str_format_internal { | ||||
| namespace { | ||||
| // clang-format off
 | ||||
| #define ABSL_LENGTH_MODS_EXPAND_ \ | ||||
|   X_VAL(h) X_SEP \ | ||||
|   X_VAL(hh) X_SEP \ | ||||
|   X_VAL(l) X_SEP \ | ||||
|   X_VAL(ll) X_SEP \ | ||||
|   X_VAL(L) X_SEP \ | ||||
|   X_VAL(j) X_SEP \ | ||||
|   X_VAL(z) X_SEP \ | ||||
|   X_VAL(t) X_SEP \ | ||||
|   X_VAL(q) | ||||
| // clang-format on
 | ||||
| }  // namespace
 | ||||
| 
 | ||||
| const LengthMod::Spec LengthMod::kSpecs[] = { | ||||
| #define X_VAL(id) { LengthMod::id, #id, strlen(#id) } | ||||
| #define X_SEP , | ||||
|     ABSL_LENGTH_MODS_EXPAND_, {LengthMod::none, "", 0} | ||||
| #undef X_VAL | ||||
| #undef X_SEP | ||||
| }; | ||||
| 
 | ||||
| const ConversionChar::Spec ConversionChar::kSpecs[] = { | ||||
| #define X_VAL(id) { ConversionChar::id, #id[0] } | ||||
| #define X_SEP , | ||||
|     ABSL_CONVERSION_CHARS_EXPAND_(X_VAL, X_SEP), | ||||
|     {ConversionChar::none, '\0'}, | ||||
| #undef X_VAL | ||||
| #undef X_SEP | ||||
| }; | ||||
| 
 | ||||
| std::string Flags::ToString() const { | ||||
|   std::string s; | ||||
|   s.append(left     ? "-" : ""); | ||||
|   s.append(show_pos ? "+" : ""); | ||||
|   s.append(sign_col ? " " : ""); | ||||
|   s.append(alt      ? "#" : ""); | ||||
|   s.append(zero     ? "0" : ""); | ||||
|   return s; | ||||
| } | ||||
| 
 | ||||
| const size_t LengthMod::kNumValues; | ||||
| 
 | ||||
| const size_t ConversionChar::kNumValues; | ||||
| 
 | ||||
| bool FormatSinkImpl::PutPaddedString(string_view v, int w, int p, bool l) { | ||||
|   size_t space_remaining = 0; | ||||
|   if (w >= 0) space_remaining = w; | ||||
|   size_t n = v.size(); | ||||
|   if (p >= 0) n = std::min(n, static_cast<size_t>(p)); | ||||
|   string_view shown(v.data(), n); | ||||
|   space_remaining = Excess(shown.size(), space_remaining); | ||||
|   if (!l) Append(space_remaining, ' '); | ||||
|   Append(shown); | ||||
|   if (l) Append(space_remaining, ' '); | ||||
|   return true; | ||||
| } | ||||
| 
 | ||||
| }  // namespace str_format_internal
 | ||||
| }  // namespace absl
 | ||||
							
								
								
									
										406
									
								
								absl/strings/internal/str_format/extension.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										406
									
								
								absl/strings/internal/str_format/extension.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,406 @@ | |||
| //
 | ||||
| // Copyright 2017 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
 | ||||
| //
 | ||||
| //      http://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_EXTENSION_H_ | ||||
| #define ABSL_STRINGS_INTERNAL_STR_FORMAT_EXTENSION_H_ | ||||
| 
 | ||||
| #include <limits.h> | ||||
| #include <cstring> | ||||
| #include <ostream> | ||||
| 
 | ||||
| #include "absl/base/port.h" | ||||
| #include "absl/strings/internal/str_format/output.h" | ||||
| #include "absl/strings/string_view.h" | ||||
| 
 | ||||
| class Cord; | ||||
| 
 | ||||
| namespace absl { | ||||
| 
 | ||||
| namespace str_format_internal { | ||||
| 
 | ||||
| class FormatRawSinkImpl { | ||||
|  public: | ||||
|   // Implicitly convert from any type that provides the hook function as
 | ||||
|   // described above.
 | ||||
|   template <typename T, decltype(str_format_internal::InvokeFlush( | ||||
|                             std::declval<T*>(), string_view()))* = nullptr> | ||||
|   FormatRawSinkImpl(T* raw)  // NOLINT
 | ||||
|       : sink_(raw), write_(&FormatRawSinkImpl::Flush<T>) {} | ||||
| 
 | ||||
|   void Write(string_view s) { write_(sink_, s); } | ||||
| 
 | ||||
|   template <typename T> | ||||
|   static FormatRawSinkImpl Extract(T s) { | ||||
|     return s.sink_; | ||||
|   } | ||||
| 
 | ||||
|  private: | ||||
|   template <typename T> | ||||
|   static void Flush(void* r, string_view s) { | ||||
|     str_format_internal::InvokeFlush(static_cast<T*>(r), s); | ||||
|   } | ||||
| 
 | ||||
|   void* sink_; | ||||
|   void (*write_)(void*, string_view); | ||||
| }; | ||||
| 
 | ||||
| // An abstraction to which conversions write their std::string data.
 | ||||
| class FormatSinkImpl { | ||||
|  public: | ||||
|   explicit FormatSinkImpl(FormatRawSinkImpl raw) : raw_(raw) {} | ||||
| 
 | ||||
|   ~FormatSinkImpl() { Flush(); } | ||||
| 
 | ||||
|   void Flush() { | ||||
|     raw_.Write(string_view(buf_, pos_ - buf_)); | ||||
|     pos_ = buf_; | ||||
|   } | ||||
| 
 | ||||
|   void Append(size_t n, char c) { | ||||
|     if (n == 0) return; | ||||
|     size_ += n; | ||||
|     auto raw_append = [&](size_t count) { | ||||
|       memset(pos_, c, count); | ||||
|       pos_ += count; | ||||
|     }; | ||||
|     while (n > Avail()) { | ||||
|       n -= Avail(); | ||||
|       if (Avail() > 0) { | ||||
|         raw_append(Avail()); | ||||
|       } | ||||
|       Flush(); | ||||
|     } | ||||
|     raw_append(n); | ||||
|   } | ||||
| 
 | ||||
|   void Append(string_view v) { | ||||
|     size_t n = v.size(); | ||||
|     if (n == 0) return; | ||||
|     size_ += n; | ||||
|     if (n >= Avail()) { | ||||
|       Flush(); | ||||
|       raw_.Write(v); | ||||
|       return; | ||||
|     } | ||||
|     memcpy(pos_, v.data(), n); | ||||
|     pos_ += n; | ||||
|   } | ||||
| 
 | ||||
|   size_t size() const { return size_; } | ||||
| 
 | ||||
|   // Put 'v' to 'sink' with specified width, precision, and left flag.
 | ||||
|   bool PutPaddedString(string_view v, int w, int p, bool l); | ||||
| 
 | ||||
|   template <typename T> | ||||
|   T Wrap() { | ||||
|     return T(this); | ||||
|   } | ||||
| 
 | ||||
|   template <typename T> | ||||
|   static FormatSinkImpl* Extract(T* s) { | ||||
|     return s->sink_; | ||||
|   } | ||||
| 
 | ||||
|  private: | ||||
|   size_t Avail() const { return buf_ + sizeof(buf_) - pos_; } | ||||
| 
 | ||||
|   FormatRawSinkImpl raw_; | ||||
|   size_t size_ = 0; | ||||
|   char* pos_ = buf_; | ||||
|   char buf_[1024]; | ||||
| }; | ||||
| 
 | ||||
| struct Flags { | ||||
|   bool basic : 1;     // fastest conversion: no flags, width, or precision
 | ||||
|   bool left : 1;      // "-"
 | ||||
|   bool show_pos : 1;  // "+"
 | ||||
|   bool sign_col : 1;  // " "
 | ||||
|   bool alt : 1;       // "#"
 | ||||
|   bool zero : 1;      // "0"
 | ||||
|   std::string ToString() const; | ||||
|   friend std::ostream& operator<<(std::ostream& os, const Flags& v) { | ||||
|     return os << v.ToString(); | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| struct LengthMod { | ||||
|  public: | ||||
|   enum Id : uint8_t { | ||||
|     h, hh, l, ll, L, j, z, t, q, none | ||||
|   }; | ||||
|   static const size_t kNumValues = none + 1; | ||||
| 
 | ||||
|   LengthMod() : id_(none) {} | ||||
| 
 | ||||
|   // Index into the opaque array of LengthMod enums.
 | ||||
|   // Requires: i < kNumValues
 | ||||
|   static LengthMod FromIndex(size_t i) { | ||||
|     return LengthMod(kSpecs[i].value); | ||||
|   } | ||||
| 
 | ||||
|   static LengthMod FromId(Id id) { return LengthMod(id); } | ||||
| 
 | ||||
|   // The length modifier std::string associated with a specified LengthMod.
 | ||||
|   string_view name() const { | ||||
|     const Spec& spec = kSpecs[id_]; | ||||
|     return {spec.name, spec.name_length}; | ||||
|   } | ||||
| 
 | ||||
|   Id id() const { return id_; } | ||||
| 
 | ||||
|   friend bool operator==(const LengthMod& a, const LengthMod& b) { | ||||
|     return a.id() == b.id(); | ||||
|   } | ||||
|   friend bool operator!=(const LengthMod& a, const LengthMod& b) { | ||||
|     return !(a == b); | ||||
|   } | ||||
|   friend std::ostream& operator<<(std::ostream& os, const LengthMod& v) { | ||||
|     return os << v.name(); | ||||
|   } | ||||
| 
 | ||||
|  private: | ||||
|   struct Spec { | ||||
|     Id value; | ||||
|     const char *name; | ||||
|     size_t name_length; | ||||
|   }; | ||||
|   static const Spec kSpecs[]; | ||||
| 
 | ||||
|   explicit LengthMod(Id id) : id_(id) {} | ||||
| 
 | ||||
|   Id id_; | ||||
| }; | ||||
| 
 | ||||
| // clang-format off
 | ||||
| #define ABSL_CONVERSION_CHARS_EXPAND_(X_VAL, X_SEP) \ | ||||
|   /* text */ \ | ||||
|   X_VAL(c) X_SEP X_VAL(C) X_SEP X_VAL(s) X_SEP X_VAL(S) X_SEP \ | ||||
|   /* ints */ \ | ||||
|   X_VAL(d) X_SEP X_VAL(i) X_SEP X_VAL(o) X_SEP \ | ||||
|   X_VAL(u) X_SEP X_VAL(x) X_SEP X_VAL(X) X_SEP \ | ||||
|   /* floats */ \ | ||||
|   X_VAL(f) X_SEP X_VAL(F) X_SEP X_VAL(e) X_SEP X_VAL(E) X_SEP \ | ||||
|   X_VAL(g) X_SEP X_VAL(G) X_SEP X_VAL(a) X_SEP X_VAL(A) X_SEP \ | ||||
|   /* misc */ \ | ||||
|   X_VAL(n) X_SEP X_VAL(p) | ||||
| // clang-format on
 | ||||
| 
 | ||||
| struct ConversionChar { | ||||
|  public: | ||||
|   enum Id : uint8_t { | ||||
|     c, C, s, S,              // text
 | ||||
|     d, i, o, u, x, X,        // int
 | ||||
|     f, F, e, E, g, G, a, A,  // float
 | ||||
|     n, p,                    // misc
 | ||||
|     none | ||||
|   }; | ||||
|   static const size_t kNumValues = none + 1; | ||||
| 
 | ||||
|   ConversionChar() : id_(none) {} | ||||
| 
 | ||||
|  public: | ||||
|   // Index into the opaque array of ConversionChar enums.
 | ||||
|   // Requires: i < kNumValues
 | ||||
|   static ConversionChar FromIndex(size_t i) { | ||||
|     return ConversionChar(kSpecs[i].value); | ||||
|   } | ||||
| 
 | ||||
|   static ConversionChar FromChar(char c) { | ||||
|     ConversionChar::Id out_id = ConversionChar::none; | ||||
|     switch (c) { | ||||
| #define X_VAL(id)                \ | ||||
|   case #id[0]:                   \ | ||||
|     out_id = ConversionChar::id; \ | ||||
|     break; | ||||
|       ABSL_CONVERSION_CHARS_EXPAND_(X_VAL, ) | ||||
| #undef X_VAL | ||||
|       default: | ||||
|         break; | ||||
|     } | ||||
|     return ConversionChar(out_id); | ||||
|   } | ||||
| 
 | ||||
|   static ConversionChar FromId(Id id) { return ConversionChar(id); } | ||||
|   Id id() const { return id_; } | ||||
| 
 | ||||
|   int radix() const { | ||||
|     switch (id()) { | ||||
|       case x: case X: case a: case A: case p: return 16; | ||||
|       case o: return 8; | ||||
|       default: return 10; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   bool upper() const { | ||||
|     switch (id()) { | ||||
|       case X: case F: case E: case G: case A: return true; | ||||
|       default: return false; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   bool is_signed() const { | ||||
|     switch (id()) { | ||||
|       case d: case i: return true; | ||||
|       default: return false; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   bool is_integral() const { | ||||
|     switch (id()) { | ||||
|       case d: case i: case u: case o: case x: case X: | ||||
|         return true; | ||||
|       default: return false; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   bool is_float() const { | ||||
|     switch (id()) { | ||||
|       case a: case e: case f: case g: case A: case E: case F: case G: | ||||
|         return true; | ||||
|       default: return false; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   bool IsValid() const { return id() != none; } | ||||
| 
 | ||||
|   // The associated char.
 | ||||
|   char Char() const { return kSpecs[id_].name; } | ||||
| 
 | ||||
|   friend bool operator==(const ConversionChar& a, const ConversionChar& b) { | ||||
|     return a.id() == b.id(); | ||||
|   } | ||||
|   friend bool operator!=(const ConversionChar& a, const ConversionChar& b) { | ||||
|     return !(a == b); | ||||
|   } | ||||
|   friend std::ostream& operator<<(std::ostream& os, const ConversionChar& v) { | ||||
|     char c = v.Char(); | ||||
|     if (!c) c = '?'; | ||||
|     return os << c; | ||||
|   } | ||||
| 
 | ||||
|  private: | ||||
|   struct Spec { | ||||
|     Id value; | ||||
|     char name; | ||||
|   }; | ||||
|   static const Spec kSpecs[]; | ||||
| 
 | ||||
|   explicit ConversionChar(Id id) : id_(id) {} | ||||
| 
 | ||||
|   Id id_; | ||||
| }; | ||||
| 
 | ||||
| class ConversionSpec { | ||||
|  public: | ||||
|   Flags flags() const { return flags_; } | ||||
|   LengthMod length_mod() const { return length_mod_; } | ||||
|   ConversionChar conv() const { return conv_; } | ||||
| 
 | ||||
|   // Returns the specified width. If width is unspecfied, it returns a negative
 | ||||
|   // value.
 | ||||
|   int width() const { return width_; } | ||||
|   // Returns the specified precision. If precision is unspecfied, it returns a
 | ||||
|   // negative value.
 | ||||
|   int precision() const { return precision_; } | ||||
| 
 | ||||
|   void set_flags(Flags f) { flags_ = f; } | ||||
|   void set_length_mod(LengthMod lm) { length_mod_ = lm; } | ||||
|   void set_conv(ConversionChar c) { conv_ = c; } | ||||
|   void set_width(int w) { width_ = w; } | ||||
|   void set_precision(int p) { precision_ = p; } | ||||
|   void set_left(bool b) { flags_.left = b; } | ||||
| 
 | ||||
|  private: | ||||
|   Flags flags_; | ||||
|   LengthMod length_mod_; | ||||
|   ConversionChar conv_; | ||||
|   int width_; | ||||
|   int precision_; | ||||
| }; | ||||
| 
 | ||||
| constexpr uint64_t ConversionCharToConvValue(char conv) { | ||||
|   return | ||||
| #define CONV_SET_CASE(c) \ | ||||
|   conv == #c[0] ? (uint64_t{1} << (1 + ConversionChar::Id::c)): | ||||
|       ABSL_CONVERSION_CHARS_EXPAND_(CONV_SET_CASE, ) | ||||
| #undef CONV_SET_CASE | ||||
|                   conv == '*' | ||||
|           ? 1 | ||||
|           : 0; | ||||
| } | ||||
| 
 | ||||
| enum class Conv : uint64_t { | ||||
| #define CONV_SET_CASE(c) c = ConversionCharToConvValue(#c[0]), | ||||
|   ABSL_CONVERSION_CHARS_EXPAND_(CONV_SET_CASE, ) | ||||
| #undef CONV_SET_CASE | ||||
| 
 | ||||
|   // Used for width/precision '*' specification.
 | ||||
|   star = ConversionCharToConvValue('*'), | ||||
| 
 | ||||
|   // Some predefined values:
 | ||||
|   integral = d | i | u | o | x | X, | ||||
|   floating = a | e | f | g | A | E | F | G, | ||||
|   numeric = integral | floating, | ||||
|   string = s,  // absl:ignore(std::string)
 | ||||
|   pointer = p | ||||
| }; | ||||
| 
 | ||||
| // Type safe OR operator.
 | ||||
| // We need this for two reasons:
 | ||||
| //  1. operator| on enums makes them decay to integers and the result is an
 | ||||
| //     integer. We need the result to stay as an enum.
 | ||||
| //  2. We use "enum class" which would not work even if we accepted the decay.
 | ||||
| constexpr Conv operator|(Conv a, Conv b) { | ||||
|   return Conv(static_cast<uint64_t>(a) | static_cast<uint64_t>(b)); | ||||
| } | ||||
| 
 | ||||
| // Get a conversion with a single character in it.
 | ||||
| constexpr Conv ConversionCharToConv(char c) { | ||||
|   return Conv(ConversionCharToConvValue(c)); | ||||
| } | ||||
| 
 | ||||
| // Checks whether `c` exists in `set`.
 | ||||
| constexpr bool Contains(Conv set, char c) { | ||||
|   return (static_cast<uint64_t>(set) & ConversionCharToConvValue(c)) != 0; | ||||
| } | ||||
| 
 | ||||
| // Checks whether all the characters in `c` are contained in `set`
 | ||||
| constexpr bool Contains(Conv set, Conv c) { | ||||
|   return (static_cast<uint64_t>(set) & static_cast<uint64_t>(c)) == | ||||
|          static_cast<uint64_t>(c); | ||||
| } | ||||
| 
 | ||||
| // Return type of the AbslFormatConvert() functions.
 | ||||
| // The Conv template parameter is used to inform the framework of what
 | ||||
| // conversion characters are supported by that AbslFormatConvert routine.
 | ||||
| template <Conv C> | ||||
| struct ConvertResult { | ||||
|   static constexpr Conv kConv = C; | ||||
|   bool value; | ||||
| }; | ||||
| template <Conv C> | ||||
| constexpr Conv ConvertResult<C>::kConv; | ||||
| 
 | ||||
| // Return capacity - used, clipped to a minimum of 0.
 | ||||
| inline size_t Excess(size_t used, size_t capacity) { | ||||
|   return used < capacity ? capacity - used : 0; | ||||
| } | ||||
| 
 | ||||
| }  // namespace str_format_internal
 | ||||
| 
 | ||||
| }  // namespace absl
 | ||||
| 
 | ||||
| #endif  // ABSL_STRINGS_STR_FORMAT_EXTENSION_H_
 | ||||
							
								
								
									
										65
									
								
								absl/strings/internal/str_format/extension_test.cc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								absl/strings/internal/str_format/extension_test.cc
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,65 @@ | |||
| //
 | ||||
| // Copyright 2017 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
 | ||||
| //
 | ||||
| //      http://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.
 | ||||
| //
 | ||||
| 
 | ||||
| #include "absl/strings/internal/str_format/extension.h" | ||||
| 
 | ||||
| #include <random> | ||||
| #include <string> | ||||
| #include "absl/strings/str_format.h" | ||||
| 
 | ||||
| #include "gtest/gtest.h" | ||||
| 
 | ||||
| namespace { | ||||
| 
 | ||||
| std::string MakeRandomString(size_t len) { | ||||
|   std::random_device rd; | ||||
|   std::mt19937 gen(rd()); | ||||
|   std::uniform_int_distribution<> dis('a', 'z'); | ||||
|   std::string s(len, '0'); | ||||
|   for (char& c : s) { | ||||
|     c = dis(gen); | ||||
|   } | ||||
|   return s; | ||||
| } | ||||
| 
 | ||||
| TEST(FormatExtensionTest, SinkAppendSubstring) { | ||||
|   for (size_t chunk_size : {1, 10, 100, 1000, 10000}) { | ||||
|     std::string expected, actual; | ||||
|     absl::str_format_internal::FormatSinkImpl sink(&actual); | ||||
|     for (size_t chunks = 0; chunks < 10; ++chunks) { | ||||
|       std::string rand = MakeRandomString(chunk_size); | ||||
|       expected += rand; | ||||
|       sink.Append(rand); | ||||
|     } | ||||
|     sink.Flush(); | ||||
|     EXPECT_EQ(actual, expected); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| TEST(FormatExtensionTest, SinkAppendChars) { | ||||
|   for (size_t chunk_size : {1, 10, 100, 1000, 10000}) { | ||||
|     std::string expected, actual; | ||||
|     absl::str_format_internal::FormatSinkImpl sink(&actual); | ||||
|     for (size_t chunks = 0; chunks < 10; ++chunks) { | ||||
|       std::string rand = MakeRandomString(1); | ||||
|       expected.append(chunk_size, rand[0]); | ||||
|       sink.Append(chunk_size, rand[0]); | ||||
|     } | ||||
|     sink.Flush(); | ||||
|     EXPECT_EQ(actual, expected); | ||||
|   } | ||||
| } | ||||
| }  // namespace
 | ||||
							
								
								
									
										476
									
								
								absl/strings/internal/str_format/float_conversion.cc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										476
									
								
								absl/strings/internal/str_format/float_conversion.cc
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,476 @@ | |||
| #include "absl/strings/internal/str_format/float_conversion.h" | ||||
| 
 | ||||
| #include <string.h> | ||||
| #include <algorithm> | ||||
| #include <cassert> | ||||
| #include <cmath> | ||||
| #include <string> | ||||
| 
 | ||||
| namespace absl { | ||||
| namespace str_format_internal { | ||||
| 
 | ||||
| namespace { | ||||
| 
 | ||||
| char *CopyStringTo(string_view v, char *out) { | ||||
|   std::memcpy(out, v.data(), v.size()); | ||||
|   return out + v.size(); | ||||
| } | ||||
| 
 | ||||
| template <typename Float> | ||||
| bool FallbackToSnprintf(const Float v, const ConversionSpec &conv, | ||||
|                         FormatSinkImpl *sink) { | ||||
|   int w = conv.width() >= 0 ? conv.width() : 0; | ||||
|   int p = conv.precision() >= 0 ? conv.precision() : -1; | ||||
|   char fmt[32]; | ||||
|   { | ||||
|     char *fp = fmt; | ||||
|     *fp++ = '%'; | ||||
|     fp = CopyStringTo(conv.flags().ToString(), fp); | ||||
|     fp = CopyStringTo("*.*", fp); | ||||
|     if (std::is_same<long double, Float>()) { | ||||
|       *fp++ = 'L'; | ||||
|     } | ||||
|     *fp++ = conv.conv().Char(); | ||||
|     *fp = 0; | ||||
|     assert(fp < fmt + sizeof(fmt)); | ||||
|   } | ||||
|   std::string space(512, '\0'); | ||||
|   string_view result; | ||||
|   while (true) { | ||||
|     int n = snprintf(&space[0], space.size(), fmt, w, p, v); | ||||
|     if (n < 0) return false; | ||||
|     if (static_cast<size_t>(n) < space.size()) { | ||||
|       result = string_view(space.data(), n); | ||||
|       break; | ||||
|     } | ||||
|     space.resize(n + 1); | ||||
|   } | ||||
|   sink->Append(result); | ||||
|   return true; | ||||
| } | ||||
| 
 | ||||
| // 128-bits in decimal: ceil(128*log(2)/log(10))
 | ||||
| //   or std::numeric_limits<__uint128_t>::digits10
 | ||||
| constexpr int kMaxFixedPrecision = 39; | ||||
| 
 | ||||
| constexpr int kBufferLength = /*sign*/ 1 + | ||||
|                               /*integer*/ kMaxFixedPrecision + | ||||
|                               /*point*/ 1 + | ||||
|                               /*fraction*/ kMaxFixedPrecision + | ||||
|                               /*exponent e+123*/ 5; | ||||
| 
 | ||||
| struct Buffer { | ||||
|   void push_front(char c) { | ||||
|     assert(begin > data); | ||||
|     *--begin = c; | ||||
|   } | ||||
|   void push_back(char c) { | ||||
|     assert(end < data + sizeof(data)); | ||||
|     *end++ = c; | ||||
|   } | ||||
|   void pop_back() { | ||||
|     assert(begin < end); | ||||
|     --end; | ||||
|   } | ||||
| 
 | ||||
|   char &back() { | ||||
|     assert(begin < end); | ||||
|     return end[-1]; | ||||
|   } | ||||
| 
 | ||||
|   char last_digit() const { return end[-1] == '.' ? end[-2] : end[-1]; } | ||||
| 
 | ||||
|   int size() const { return static_cast<int>(end - begin); } | ||||
| 
 | ||||
|   char data[kBufferLength]; | ||||
|   char *begin; | ||||
|   char *end; | ||||
| }; | ||||
| 
 | ||||
| enum class FormatStyle { Fixed, Precision }; | ||||
| 
 | ||||
| // If the value is Inf or Nan, print it and return true.
 | ||||
| // Otherwise, return false.
 | ||||
| template <typename Float> | ||||
| bool ConvertNonNumericFloats(char sign_char, Float v, | ||||
|                              const ConversionSpec &conv, FormatSinkImpl *sink) { | ||||
|   char text[4], *ptr = text; | ||||
|   if (sign_char) *ptr++ = sign_char; | ||||
|   if (std::isnan(v)) { | ||||
|     ptr = std::copy_n(conv.conv().upper() ? "NAN" : "nan", 3, ptr); | ||||
|   } else if (std::isinf(v)) { | ||||
|     ptr = std::copy_n(conv.conv().upper() ? "INF" : "inf", 3, ptr); | ||||
|   } else { | ||||
|     return false; | ||||
|   } | ||||
| 
 | ||||
|   return sink->PutPaddedString(string_view(text, ptr - text), conv.width(), -1, | ||||
|                                conv.flags().left); | ||||
| } | ||||
| 
 | ||||
| // Round up the last digit of the value.
 | ||||
| // It will carry over and potentially overflow. 'exp' will be adjusted in that
 | ||||
| // case.
 | ||||
| template <FormatStyle mode> | ||||
| void RoundUp(Buffer *buffer, int *exp) { | ||||
|   char *p = &buffer->back(); | ||||
|   while (p >= buffer->begin && (*p == '9' || *p == '.')) { | ||||
|     if (*p == '9') *p = '0'; | ||||
|     --p; | ||||
|   } | ||||
| 
 | ||||
|   if (p < buffer->begin) { | ||||
|     *p = '1'; | ||||
|     buffer->begin = p; | ||||
|     if (mode == FormatStyle::Precision) { | ||||
|       std::swap(p[1], p[2]);  // move the .
 | ||||
|       ++*exp; | ||||
|       buffer->pop_back(); | ||||
|     } | ||||
|   } else { | ||||
|     ++*p; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void PrintExponent(int exp, char e, Buffer *out) { | ||||
|   out->push_back(e); | ||||
|   if (exp < 0) { | ||||
|     out->push_back('-'); | ||||
|     exp = -exp; | ||||
|   } else { | ||||
|     out->push_back('+'); | ||||
|   } | ||||
|   // Exponent digits.
 | ||||
|   if (exp > 99) { | ||||
|     out->push_back(exp / 100 + '0'); | ||||
|     out->push_back(exp / 10 % 10 + '0'); | ||||
|     out->push_back(exp % 10 + '0'); | ||||
|   } else { | ||||
|     out->push_back(exp / 10 + '0'); | ||||
|     out->push_back(exp % 10 + '0'); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| template <typename Float, typename Int> | ||||
| constexpr bool CanFitMantissa() { | ||||
|   return std::numeric_limits<Float>::digits <= std::numeric_limits<Int>::digits; | ||||
| } | ||||
| 
 | ||||
| template <typename Float> | ||||
| struct Decomposed { | ||||
|   Float mantissa; | ||||
|   int exponent; | ||||
| }; | ||||
| 
 | ||||
| // Decompose the double into an integer mantissa and an exponent.
 | ||||
| template <typename Float> | ||||
| Decomposed<Float> Decompose(Float v) { | ||||
|   int exp; | ||||
|   Float m = std::frexp(v, &exp); | ||||
|   m = std::ldexp(m, std::numeric_limits<Float>::digits); | ||||
|   exp -= std::numeric_limits<Float>::digits; | ||||
|   return {m, exp}; | ||||
| } | ||||
| 
 | ||||
| // Print 'digits' as decimal.
 | ||||
| // In Fixed mode, we add a '.' at the end.
 | ||||
| // In Precision mode, we add a '.' after the first digit.
 | ||||
| template <FormatStyle mode, typename Int> | ||||
| int PrintIntegralDigits(Int digits, Buffer *out) { | ||||
|   int printed = 0; | ||||
|   if (digits) { | ||||
|     for (; digits; digits /= 10) out->push_front(digits % 10 + '0'); | ||||
|     printed = out->size(); | ||||
|     if (mode == FormatStyle::Precision) { | ||||
|       out->push_front(*out->begin); | ||||
|       out->begin[1] = '.'; | ||||
|     } else { | ||||
|       out->push_back('.'); | ||||
|     } | ||||
|   } else if (mode == FormatStyle::Fixed) { | ||||
|     out->push_front('0'); | ||||
|     out->push_back('.'); | ||||
|     printed = 1; | ||||
|   } | ||||
|   return printed; | ||||
| } | ||||
| 
 | ||||
| // Back out 'extra_digits' digits and round up if necessary.
 | ||||
| bool RemoveExtraPrecision(int extra_digits, bool has_leftover_value, | ||||
|                           Buffer *out, int *exp_out) { | ||||
|   if (extra_digits <= 0) return false; | ||||
| 
 | ||||
|   // Back out the extra digits
 | ||||
|   out->end -= extra_digits; | ||||
| 
 | ||||
|   bool needs_to_round_up = [&] { | ||||
|     // We look at the digit just past the end.
 | ||||
|     // There must be 'extra_digits' extra valid digits after end.
 | ||||
|     if (*out->end > '5') return true; | ||||
|     if (*out->end < '5') return false; | ||||
|     if (has_leftover_value || std::any_of(out->end + 1, out->end + extra_digits, | ||||
|                                           [](char c) { return c != '0'; })) | ||||
|       return true; | ||||
| 
 | ||||
|     // Ends in ...50*, round to even.
 | ||||
|     return out->last_digit() % 2 == 1; | ||||
|   }(); | ||||
| 
 | ||||
|   if (needs_to_round_up) { | ||||
|     RoundUp<FormatStyle::Precision>(out, exp_out); | ||||
|   } | ||||
|   return true; | ||||
| } | ||||
| 
 | ||||
| // Print the value into the buffer.
 | ||||
| // This will not include the exponent, which will be returned in 'exp_out' for
 | ||||
| // Precision mode.
 | ||||
| template <typename Int, typename Float, FormatStyle mode> | ||||
| bool FloatToBufferImpl(Int int_mantissa, int exp, int precision, Buffer *out, | ||||
|                        int *exp_out) { | ||||
|   assert((CanFitMantissa<Float, Int>())); | ||||
| 
 | ||||
|   const int int_bits = std::numeric_limits<Int>::digits; | ||||
| 
 | ||||
|   // In precision mode, we start printing one char to the right because it will
 | ||||
|   // also include the '.'
 | ||||
|   // In fixed mode we put the dot afterwards on the right.
 | ||||
|   out->begin = out->end = | ||||
|       out->data + 1 + kMaxFixedPrecision + (mode == FormatStyle::Precision); | ||||
| 
 | ||||
|   if (exp >= 0) { | ||||
|     if (std::numeric_limits<Float>::digits + exp > int_bits) { | ||||
|       // The value will overflow the Int
 | ||||
|       return false; | ||||
|     } | ||||
|     int digits_printed = PrintIntegralDigits<mode>(int_mantissa << exp, out); | ||||
|     int digits_to_zero_pad = precision; | ||||
|     if (mode == FormatStyle::Precision) { | ||||
|       *exp_out = digits_printed - 1; | ||||
|       digits_to_zero_pad -= digits_printed - 1; | ||||
|       if (RemoveExtraPrecision(-digits_to_zero_pad, false, out, exp_out)) { | ||||
|         return true; | ||||
|       } | ||||
|     } | ||||
|     for (; digits_to_zero_pad-- > 0;) out->push_back('0'); | ||||
|     return true; | ||||
|   } | ||||
| 
 | ||||
|   exp = -exp; | ||||
|   // We need at least 4 empty bits for the next decimal digit.
 | ||||
|   // We will multiply by 10.
 | ||||
|   if (exp > int_bits - 4) return false; | ||||
| 
 | ||||
|   const Int mask = (Int{1} << exp) - 1; | ||||
| 
 | ||||
|   // Print the integral part first.
 | ||||
|   int digits_printed = PrintIntegralDigits<mode>(int_mantissa >> exp, out); | ||||
|   int_mantissa &= mask; | ||||
| 
 | ||||
|   int fractional_count = precision; | ||||
|   if (mode == FormatStyle::Precision) { | ||||
|     if (digits_printed == 0) { | ||||
|       // Find the first non-zero digit, when in Precision mode.
 | ||||
|       *exp_out = 0; | ||||
|       if (int_mantissa) { | ||||
|         while (int_mantissa <= mask) { | ||||
|           int_mantissa *= 10; | ||||
|           --*exp_out; | ||||
|         } | ||||
|       } | ||||
|       out->push_front(static_cast<char>(int_mantissa >> exp) + '0'); | ||||
|       out->push_back('.'); | ||||
|       int_mantissa &= mask; | ||||
|     } else { | ||||
|       // We already have a digit, and a '.'
 | ||||
|       *exp_out = digits_printed - 1; | ||||
|       fractional_count -= *exp_out; | ||||
|       if (RemoveExtraPrecision(-fractional_count, int_mantissa != 0, out, | ||||
|                                exp_out)) { | ||||
|         // If we had enough digits, return right away.
 | ||||
|         // The code below will try to round again otherwise.
 | ||||
|         return true; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   auto get_next_digit = [&] { | ||||
|     int_mantissa *= 10; | ||||
|     int digit = static_cast<int>(int_mantissa >> exp); | ||||
|     int_mantissa &= mask; | ||||
|     return digit; | ||||
|   }; | ||||
| 
 | ||||
|   // Print fractional_count more digits, if available.
 | ||||
|   for (; fractional_count > 0; --fractional_count) { | ||||
|     out->push_back(get_next_digit() + '0'); | ||||
|   } | ||||
| 
 | ||||
|   int next_digit = get_next_digit(); | ||||
|   if (next_digit > 5 || | ||||
|       (next_digit == 5 && (int_mantissa || out->last_digit() % 2 == 1))) { | ||||
|     RoundUp<mode>(out, exp_out); | ||||
|   } | ||||
| 
 | ||||
|   return true; | ||||
| } | ||||
| 
 | ||||
| template <FormatStyle mode, typename Float> | ||||
| bool FloatToBuffer(Decomposed<Float> decomposed, int precision, Buffer *out, | ||||
|                    int *exp) { | ||||
|   if (precision > kMaxFixedPrecision) return false; | ||||
| 
 | ||||
|   // Try with uint64_t.
 | ||||
|   if (CanFitMantissa<Float, std::uint64_t>() && | ||||
|       FloatToBufferImpl<std::uint64_t, Float, mode>( | ||||
|           static_cast<std::uint64_t>(decomposed.mantissa), | ||||
|           static_cast<std::uint64_t>(decomposed.exponent), precision, out, exp)) | ||||
|     return true; | ||||
| 
 | ||||
| #if defined(__SIZEOF_INT128__) | ||||
|   // If that is not enough, try with __uint128_t.
 | ||||
|   return CanFitMantissa<Float, __uint128_t>() && | ||||
|          FloatToBufferImpl<__uint128_t, Float, mode>( | ||||
|              static_cast<__uint128_t>(decomposed.mantissa), | ||||
|              static_cast<__uint128_t>(decomposed.exponent), precision, out, | ||||
|              exp); | ||||
| #endif | ||||
|   return false; | ||||
| } | ||||
| 
 | ||||
| void WriteBufferToSink(char sign_char, string_view str, | ||||
|                        const ConversionSpec &conv, FormatSinkImpl *sink) { | ||||
|   int left_spaces = 0, zeros = 0, right_spaces = 0; | ||||
|   int missing_chars = | ||||
|       conv.width() >= 0 ? std::max(conv.width() - static_cast<int>(str.size()) - | ||||
|                                        static_cast<int>(sign_char != 0), | ||||
|                                    0) | ||||
|                         : 0; | ||||
|   if (conv.flags().left) { | ||||
|     right_spaces = missing_chars; | ||||
|   } else if (conv.flags().zero) { | ||||
|     zeros = missing_chars; | ||||
|   } else { | ||||
|     left_spaces = missing_chars; | ||||
|   } | ||||
| 
 | ||||
|   sink->Append(left_spaces, ' '); | ||||
|   if (sign_char) sink->Append(1, sign_char); | ||||
|   sink->Append(zeros, '0'); | ||||
|   sink->Append(str); | ||||
|   sink->Append(right_spaces, ' '); | ||||
| } | ||||
| 
 | ||||
| template <typename Float> | ||||
| bool FloatToSink(const Float v, const ConversionSpec &conv, | ||||
|                  FormatSinkImpl *sink) { | ||||
|   // Print the sign or the sign column.
 | ||||
|   Float abs_v = v; | ||||
|   char sign_char = 0; | ||||
|   if (std::signbit(abs_v)) { | ||||
|     sign_char = '-'; | ||||
|     abs_v = -abs_v; | ||||
|   } else if (conv.flags().show_pos) { | ||||
|     sign_char = '+'; | ||||
|   } else if (conv.flags().sign_col) { | ||||
|     sign_char = ' '; | ||||
|   } | ||||
| 
 | ||||
|   // Print nan/inf.
 | ||||
|   if (ConvertNonNumericFloats(sign_char, abs_v, conv, sink)) { | ||||
|     return true; | ||||
|   } | ||||
| 
 | ||||
|   int precision = conv.precision() < 0 ? 6 : conv.precision(); | ||||
| 
 | ||||
|   int exp = 0; | ||||
| 
 | ||||
|   auto decomposed = Decompose(abs_v); | ||||
| 
 | ||||
|   Buffer buffer; | ||||
| 
 | ||||
|   switch (conv.conv().id()) { | ||||
|     case ConversionChar::f: | ||||
|     case ConversionChar::F: | ||||
|       if (!FloatToBuffer<FormatStyle::Fixed>(decomposed, precision, &buffer, | ||||
|                                              nullptr)) { | ||||
|         return FallbackToSnprintf(v, conv, sink); | ||||
|       } | ||||
|       if (!conv.flags().alt && buffer.back() == '.') buffer.pop_back(); | ||||
|       break; | ||||
| 
 | ||||
|     case ConversionChar::e: | ||||
|     case ConversionChar::E: | ||||
|       if (!FloatToBuffer<FormatStyle::Precision>(decomposed, precision, &buffer, | ||||
|                                                  &exp)) { | ||||
|         return FallbackToSnprintf(v, conv, sink); | ||||
|       } | ||||
|       if (!conv.flags().alt && buffer.back() == '.') buffer.pop_back(); | ||||
|       PrintExponent(exp, conv.conv().upper() ? 'E' : 'e', &buffer); | ||||
|       break; | ||||
| 
 | ||||
|     case ConversionChar::g: | ||||
|     case ConversionChar::G: | ||||
|       precision = std::max(0, precision - 1); | ||||
|       if (!FloatToBuffer<FormatStyle::Precision>(decomposed, precision, &buffer, | ||||
|                                                  &exp)) { | ||||
|         return FallbackToSnprintf(v, conv, sink); | ||||
|       } | ||||
|       if (precision + 1 > exp && exp >= -4) { | ||||
|         if (exp < 0) { | ||||
|           // Have 1.23456, needs 0.00123456
 | ||||
|           // Move the first digit
 | ||||
|           buffer.begin[1] = *buffer.begin; | ||||
|           // Add some zeros
 | ||||
|           for (; exp < -1; ++exp) *buffer.begin-- = '0'; | ||||
|           *buffer.begin-- = '.'; | ||||
|           *buffer.begin = '0'; | ||||
|         } else if (exp > 0) { | ||||
|           // Have 1.23456, needs 1234.56
 | ||||
|           // Move the '.' exp positions to the right.
 | ||||
|           std::rotate(buffer.begin + 1, buffer.begin + 2, | ||||
|                       buffer.begin + exp + 2); | ||||
|         } | ||||
|         exp = 0; | ||||
|       } | ||||
|       if (!conv.flags().alt) { | ||||
|         while (buffer.back() == '0') buffer.pop_back(); | ||||
|         if (buffer.back() == '.') buffer.pop_back(); | ||||
|       } | ||||
|       if (exp) PrintExponent(exp, conv.conv().upper() ? 'E' : 'e', &buffer); | ||||
|       break; | ||||
| 
 | ||||
|     case ConversionChar::a: | ||||
|     case ConversionChar::A: | ||||
|       return FallbackToSnprintf(v, conv, sink); | ||||
| 
 | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| 
 | ||||
|   WriteBufferToSink(sign_char, | ||||
|                     string_view(buffer.begin, buffer.end - buffer.begin), conv, | ||||
|                     sink); | ||||
| 
 | ||||
|   return true; | ||||
| } | ||||
| 
 | ||||
| }  // namespace
 | ||||
| 
 | ||||
| bool ConvertFloatImpl(long double v, const ConversionSpec &conv, | ||||
|                       FormatSinkImpl *sink) { | ||||
|   return FloatToSink(v, conv, sink); | ||||
| } | ||||
| 
 | ||||
| bool ConvertFloatImpl(float v, const ConversionSpec &conv, | ||||
|                       FormatSinkImpl *sink) { | ||||
|   return FloatToSink(v, conv, sink); | ||||
| } | ||||
| 
 | ||||
| bool ConvertFloatImpl(double v, const ConversionSpec &conv, | ||||
|                       FormatSinkImpl *sink) { | ||||
|   return FloatToSink(v, conv, sink); | ||||
| } | ||||
| 
 | ||||
| }  // namespace str_format_internal
 | ||||
| }  // namespace absl
 | ||||
							
								
								
									
										21
									
								
								absl/strings/internal/str_format/float_conversion.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								absl/strings/internal/str_format/float_conversion.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,21 @@ | |||
| #ifndef ABSL_STRINGS_INTERNAL_STR_FORMAT_FLOAT_CONVERSION_H_ | ||||
| #define ABSL_STRINGS_INTERNAL_STR_FORMAT_FLOAT_CONVERSION_H_ | ||||
| 
 | ||||
| #include "absl/strings/internal/str_format/extension.h" | ||||
| 
 | ||||
| namespace absl { | ||||
| namespace str_format_internal { | ||||
| 
 | ||||
| bool ConvertFloatImpl(float v, const ConversionSpec &conv, | ||||
|                       FormatSinkImpl *sink); | ||||
| 
 | ||||
| bool ConvertFloatImpl(double v, const ConversionSpec &conv, | ||||
|                       FormatSinkImpl *sink); | ||||
| 
 | ||||
| bool ConvertFloatImpl(long double v, const ConversionSpec &conv, | ||||
|                       FormatSinkImpl *sink); | ||||
| 
 | ||||
| }  // namespace str_format_internal
 | ||||
| }  // namespace absl
 | ||||
| 
 | ||||
| #endif  // ABSL_STRINGS_INTERNAL_STR_FORMAT_FLOAT_CONVERSION_H_
 | ||||
							
								
								
									
										47
									
								
								absl/strings/internal/str_format/output.cc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								absl/strings/internal/str_format/output.cc
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,47 @@ | |||
| // Copyright 2017 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
 | ||||
| //
 | ||||
| //      http://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.
 | ||||
| 
 | ||||
| #include "absl/strings/internal/str_format/output.h" | ||||
| 
 | ||||
| #include <errno.h> | ||||
| #include <cstring> | ||||
| 
 | ||||
| namespace absl { | ||||
| namespace str_format_internal { | ||||
| 
 | ||||
| void BufferRawSink::Write(string_view v) { | ||||
|   size_t to_write = std::min(v.size(), size_); | ||||
|   std::memcpy(buffer_, v.data(), to_write); | ||||
|   buffer_ += to_write; | ||||
|   size_ -= to_write; | ||||
|   total_written_ += v.size(); | ||||
| } | ||||
| 
 | ||||
| void FILERawSink::Write(string_view v) { | ||||
|   while (!v.empty() && !error_) { | ||||
|     if (size_t result = std::fwrite(v.data(), 1, v.size(), output_)) { | ||||
|       // Some progress was made.
 | ||||
|       count_ += result; | ||||
|       v.remove_prefix(result); | ||||
|     } else { | ||||
|       // Some error occurred.
 | ||||
|       if (errno != EINTR) { | ||||
|         error_ = errno; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| }  // namespace str_format_internal
 | ||||
| }  // namespace absl
 | ||||
							
								
								
									
										101
									
								
								absl/strings/internal/str_format/output.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								absl/strings/internal/str_format/output.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,101 @@ | |||
| // Copyright 2017 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
 | ||||
| //
 | ||||
| //      http://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.
 | ||||
| //
 | ||||
| // Output extension hooks for the Format library.
 | ||||
| // `internal::InvokeFlush` calls the appropriate flush function for the
 | ||||
| // specified output argument.
 | ||||
| // `BufferRawSink` is a simple output sink for a char buffer. Used by SnprintF.
 | ||||
| // `FILERawSink` is a std::FILE* based sink. Used by PrintF and FprintF.
 | ||||
| 
 | ||||
| #ifndef ABSL_STRINGS_INTERNAL_STR_FORMAT_OUTPUT_H_ | ||||
| #define ABSL_STRINGS_INTERNAL_STR_FORMAT_OUTPUT_H_ | ||||
| 
 | ||||
| #include <cstdio> | ||||
| #include <ostream> | ||||
| #include <string> | ||||
| 
 | ||||
| #include "absl/base/port.h" | ||||
| #include "absl/strings/string_view.h" | ||||
| 
 | ||||
| class Cord; | ||||
| 
 | ||||
| namespace absl { | ||||
| namespace str_format_internal { | ||||
| 
 | ||||
| // RawSink implementation that writes into a char* buffer.
 | ||||
| // It will not overflow the buffer, but will keep the total count of chars
 | ||||
| // that would have been written.
 | ||||
| class BufferRawSink { | ||||
|  public: | ||||
|   BufferRawSink(char* buffer, size_t size) : buffer_(buffer), size_(size) {} | ||||
| 
 | ||||
|   size_t total_written() const { return total_written_; } | ||||
|   void Write(string_view v); | ||||
| 
 | ||||
|  private: | ||||
|   char* buffer_; | ||||
|   size_t size_; | ||||
|   size_t total_written_ = 0; | ||||
| }; | ||||
| 
 | ||||
| // RawSink implementation that writes into a FILE*.
 | ||||
| // It keeps track of the total number of bytes written and any error encountered
 | ||||
| // during the writes.
 | ||||
| class FILERawSink { | ||||
|  public: | ||||
|   explicit FILERawSink(std::FILE* output) : output_(output) {} | ||||
| 
 | ||||
|   void Write(string_view v); | ||||
| 
 | ||||
|   size_t count() const { return count_; } | ||||
|   int error() const { return error_; } | ||||
| 
 | ||||
|  private: | ||||
|   std::FILE* output_; | ||||
|   int error_ = 0; | ||||
|   size_t count_ = 0; | ||||
| }; | ||||
| 
 | ||||
| // Provide RawSink integration with common types from the STL.
 | ||||
| inline void AbslFormatFlush(std::string* out, string_view s) { | ||||
|   out->append(s.begin(), s.size()); | ||||
| } | ||||
| inline void AbslFormatFlush(std::ostream* out, string_view s) { | ||||
|   out->write(s.begin(), s.size()); | ||||
| } | ||||
| 
 | ||||
| template <class AbslCord, typename = typename std::enable_if< | ||||
|                               std::is_same<AbslCord, ::Cord>::value>::type> | ||||
| inline void AbslFormatFlush(AbslCord* out, string_view s) { | ||||
|   out->Append(s); | ||||
| } | ||||
| 
 | ||||
| inline void AbslFormatFlush(FILERawSink* sink, string_view v) { | ||||
|   sink->Write(v); | ||||
| } | ||||
| 
 | ||||
| inline void AbslFormatFlush(BufferRawSink* sink, string_view v) { | ||||
|   sink->Write(v); | ||||
| } | ||||
| 
 | ||||
| template <typename T> | ||||
| auto InvokeFlush(T* out, string_view s) | ||||
|     -> decltype(str_format_internal::AbslFormatFlush(out, s)) { | ||||
|   str_format_internal::AbslFormatFlush(out, s); | ||||
| } | ||||
| 
 | ||||
| }  // namespace str_format_internal
 | ||||
| }  // namespace absl
 | ||||
| 
 | ||||
| #endif  // ABSL_STRINGS_INTERNAL_STR_FORMAT_OUTPUT_H_
 | ||||
							
								
								
									
										78
									
								
								absl/strings/internal/str_format/output_test.cc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								absl/strings/internal/str_format/output_test.cc
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,78 @@ | |||
| // Copyright 2017 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
 | ||||
| //
 | ||||
| //      http://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.
 | ||||
| 
 | ||||
| #include "absl/strings/internal/str_format/output.h" | ||||
| 
 | ||||
| #include <sstream> | ||||
| #include <string> | ||||
| 
 | ||||
| 
 | ||||
| #include "gmock/gmock.h" | ||||
| #include "gtest/gtest.h" | ||||
| 
 | ||||
| namespace absl { | ||||
| namespace { | ||||
| 
 | ||||
| TEST(InvokeFlush, String) { | ||||
|   std::string str = "ABC"; | ||||
|   str_format_internal::InvokeFlush(&str, "DEF"); | ||||
|   EXPECT_EQ(str, "ABCDEF"); | ||||
| 
 | ||||
| #if UTIL_FORMAT_HAS_GLOBAL_STRING | ||||
|   std::string str2 = "ABC"; | ||||
|   str_format_internal::InvokeFlush(&str2, "DEF"); | ||||
|   EXPECT_EQ(str2, "ABCDEF"); | ||||
| #endif  // UTIL_FORMAT_HAS_GLOBAL_STRING
 | ||||
| } | ||||
| 
 | ||||
| TEST(InvokeFlush, Stream) { | ||||
|   std::stringstream str; | ||||
|   str << "ABC"; | ||||
|   str_format_internal::InvokeFlush(&str, "DEF"); | ||||
|   EXPECT_EQ(str.str(), "ABCDEF"); | ||||
| } | ||||
| 
 | ||||
| TEST(BufferRawSink, Limits) { | ||||
|   char buf[16]; | ||||
|   { | ||||
|     std::fill(std::begin(buf), std::end(buf), 'x'); | ||||
|     str_format_internal::BufferRawSink bufsink(buf, sizeof(buf) - 1); | ||||
|     str_format_internal::InvokeFlush(&bufsink, "Hello World237"); | ||||
|     EXPECT_EQ(std::string(buf, sizeof(buf)), "Hello World237xx"); | ||||
|   } | ||||
|   { | ||||
|     std::fill(std::begin(buf), std::end(buf), 'x'); | ||||
|     str_format_internal::BufferRawSink bufsink(buf, sizeof(buf) - 1); | ||||
|     str_format_internal::InvokeFlush(&bufsink, "Hello World237237"); | ||||
|     EXPECT_EQ(std::string(buf, sizeof(buf)), "Hello World2372x"); | ||||
|   } | ||||
|   { | ||||
|     std::fill(std::begin(buf), std::end(buf), 'x'); | ||||
|     str_format_internal::BufferRawSink bufsink(buf, sizeof(buf) - 1); | ||||
|     str_format_internal::InvokeFlush(&bufsink, "Hello World"); | ||||
|     str_format_internal::InvokeFlush(&bufsink, "237"); | ||||
|     EXPECT_EQ(std::string(buf, sizeof(buf)), "Hello World237xx"); | ||||
|   } | ||||
|   { | ||||
|     std::fill(std::begin(buf), std::end(buf), 'x'); | ||||
|     str_format_internal::BufferRawSink bufsink(buf, sizeof(buf) - 1); | ||||
|     str_format_internal::InvokeFlush(&bufsink, "Hello World"); | ||||
|     str_format_internal::InvokeFlush(&bufsink, "237237"); | ||||
|     EXPECT_EQ(std::string(buf, sizeof(buf)), "Hello World2372x"); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| }  // namespace
 | ||||
| }  // namespace absl
 | ||||
| 
 | ||||
							
								
								
									
										294
									
								
								absl/strings/internal/str_format/parser.cc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										294
									
								
								absl/strings/internal/str_format/parser.cc
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,294 @@ | |||
| #include "absl/strings/internal/str_format/parser.h" | ||||
| 
 | ||||
| #include <assert.h> | ||||
| #include <string.h> | ||||
| #include <wchar.h> | ||||
| #include <cctype> | ||||
| #include <cstdint> | ||||
| 
 | ||||
| #include <algorithm> | ||||
| #include <initializer_list> | ||||
| #include <limits> | ||||
| #include <ostream> | ||||
| #include <string> | ||||
| #include <unordered_set> | ||||
| 
 | ||||
| namespace absl { | ||||
| namespace str_format_internal { | ||||
| namespace { | ||||
| 
 | ||||
| bool CheckFastPathSetting(const UnboundConversion& conv) { | ||||
|   bool should_be_basic = !conv.flags.left &&      //
 | ||||
|                          !conv.flags.show_pos &&  //
 | ||||
|                          !conv.flags.sign_col &&  //
 | ||||
|                          !conv.flags.alt &&       //
 | ||||
|                          !conv.flags.zero &&      //
 | ||||
|                          (conv.width.value() == -1) && | ||||
|                          (conv.precision.value() == -1); | ||||
|   if (should_be_basic != conv.flags.basic) { | ||||
|     fprintf(stderr, | ||||
|             "basic=%d left=%d show_pos=%d sign_col=%d alt=%d zero=%d " | ||||
|             "width=%d precision=%d\n", | ||||
|             conv.flags.basic, conv.flags.left, conv.flags.show_pos, | ||||
|             conv.flags.sign_col, conv.flags.alt, conv.flags.zero, | ||||
|             conv.width.value(), conv.precision.value()); | ||||
|   } | ||||
|   return should_be_basic == conv.flags.basic; | ||||
| } | ||||
| 
 | ||||
| // Keep a single table for all the conversion chars and length modifiers.
 | ||||
| // We invert the length modifiers to make them negative so that we can easily
 | ||||
| // test for them.
 | ||||
| // Everything else is `none`, which is a negative constant.
 | ||||
| using CC = ConversionChar::Id; | ||||
| using LM = LengthMod::Id; | ||||
| static constexpr std::int8_t none = -128; | ||||
| static constexpr std::int8_t kIds[] = { | ||||
|     none,   none,   none,   none,  none,   none,  none,  none,   // 00-07
 | ||||
|     none,   none,   none,   none,  none,   none,  none,  none,   // 08-0f
 | ||||
|     none,   none,   none,   none,  none,   none,  none,  none,   // 10-17
 | ||||
|     none,   none,   none,   none,  none,   none,  none,  none,   // 18-1f
 | ||||
|     none,   none,   none,   none,  none,   none,  none,  none,   // 20-27
 | ||||
|     none,   none,   none,   none,  none,   none,  none,  none,   // 28-2f
 | ||||
|     none,   none,   none,   none,  none,   none,  none,  none,   // 30-37
 | ||||
|     none,   none,   none,   none,  none,   none,  none,  none,   // 38-3f
 | ||||
|     none,   CC::A,  none,   CC::C, none,   CC::E, CC::F, CC::G,  // @ABCDEFG
 | ||||
|     none,   none,   none,   none,  ~LM::L, none,  none,  none,   // HIJKLMNO
 | ||||
|     none,   none,   none,   CC::S, none,   none,  none,  none,   // PQRSTUVW
 | ||||
|     CC::X,  none,   none,   none,  none,   none,  none,  none,   // XYZ[\]^_
 | ||||
|     none,   CC::a,  none,   CC::c, CC::d,  CC::e, CC::f, CC::g,  // `abcdefg
 | ||||
|     ~LM::h, CC::i,  ~LM::j, none,  ~LM::l, none,  CC::n, CC::o,  // hijklmno
 | ||||
|     CC::p,  ~LM::q, none,   CC::s, ~LM::t, CC::u, none,  none,   // pqrstuvw
 | ||||
|     CC::x,  none,   ~LM::z, none,  none,   none,  none,  none,   // xyz{|}~!
 | ||||
|     none,   none,   none,   none,  none,   none,  none,  none,   // 80-87
 | ||||
|     none,   none,   none,   none,  none,   none,  none,  none,   // 88-8f
 | ||||
|     none,   none,   none,   none,  none,   none,  none,  none,   // 90-97
 | ||||
|     none,   none,   none,   none,  none,   none,  none,  none,   // 98-9f
 | ||||
|     none,   none,   none,   none,  none,   none,  none,  none,   // a0-a7
 | ||||
|     none,   none,   none,   none,  none,   none,  none,  none,   // a8-af
 | ||||
|     none,   none,   none,   none,  none,   none,  none,  none,   // b0-b7
 | ||||
|     none,   none,   none,   none,  none,   none,  none,  none,   // b8-bf
 | ||||
|     none,   none,   none,   none,  none,   none,  none,  none,   // c0-c7
 | ||||
|     none,   none,   none,   none,  none,   none,  none,  none,   // c8-cf
 | ||||
|     none,   none,   none,   none,  none,   none,  none,  none,   // d0-d7
 | ||||
|     none,   none,   none,   none,  none,   none,  none,  none,   // d8-df
 | ||||
|     none,   none,   none,   none,  none,   none,  none,  none,   // e0-e7
 | ||||
|     none,   none,   none,   none,  none,   none,  none,  none,   // e8-ef
 | ||||
|     none,   none,   none,   none,  none,   none,  none,  none,   // f0-f7
 | ||||
|     none,   none,   none,   none,  none,   none,  none,  none,   // f8-ff
 | ||||
| }; | ||||
| 
 | ||||
| template <bool is_positional> | ||||
| bool ConsumeConversion(string_view *src, UnboundConversion *conv, | ||||
|                        int *next_arg) { | ||||
|   const char *pos = src->begin(); | ||||
|   const char *const end = src->end(); | ||||
|   char c; | ||||
|   // Read the next char into `c` and update `pos`. Reads '\0' if at end.
 | ||||
|   const auto get_char = [&] { c = pos == end ? '\0' : *pos++; }; | ||||
| 
 | ||||
|   const auto parse_digits = [&] { | ||||
|     int digits = c - '0'; | ||||
|     // We do not want to overflow `digits` so we consume at most digits10-1
 | ||||
|     // 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<int>::digits10 - 2; | ||||
|     for (get_char(); num_digits && std::isdigit(c); get_char()) { | ||||
|       --num_digits; | ||||
|       digits = 10 * digits + c - '0'; | ||||
|     } | ||||
|     return digits; | ||||
|   }; | ||||
| 
 | ||||
|   if (is_positional) { | ||||
|     get_char(); | ||||
|     if (c < '1' || c > '9') return false; | ||||
|     conv->arg_position = parse_digits(); | ||||
|     assert(conv->arg_position > 0); | ||||
|     if (c != '$') return false; | ||||
|   } | ||||
| 
 | ||||
|   get_char(); | ||||
| 
 | ||||
|   // We should start with the basic flag on.
 | ||||
|   assert(conv->flags.basic); | ||||
| 
 | ||||
|   // 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') { | ||||
|     conv->flags.basic = false; | ||||
| 
 | ||||
|     for (; c <= '0'; get_char()) { | ||||
|       switch (c) { | ||||
|         case '-': | ||||
|           conv->flags.left = true; | ||||
|           continue; | ||||
|         case '+': | ||||
|           conv->flags.show_pos = true; | ||||
|           continue; | ||||
|         case ' ': | ||||
|           conv->flags.sign_col = true; | ||||
|           continue; | ||||
|         case '#': | ||||
|           conv->flags.alt = true; | ||||
|           continue; | ||||
|         case '0': | ||||
|           conv->flags.zero = true; | ||||
|           continue; | ||||
|       } | ||||
|       break; | ||||
|     } | ||||
| 
 | ||||
|     if (c <= '9') { | ||||
|       if (c >= '0') { | ||||
|         int maybe_width = parse_digits(); | ||||
|         if (!is_positional && c == '$') { | ||||
|           if (*next_arg != 0) return false; | ||||
|           // Positional conversion.
 | ||||
|           *next_arg = -1; | ||||
|           conv->flags = Flags(); | ||||
|           conv->flags.basic = true; | ||||
|           return ConsumeConversion<true>(src, conv, next_arg); | ||||
|         } | ||||
|         conv->width.set_value(maybe_width); | ||||
|       } else if (c == '*') { | ||||
|         get_char(); | ||||
|         if (is_positional) { | ||||
|           if (c < '1' || c > '9') return false; | ||||
|           conv->width.set_from_arg(parse_digits()); | ||||
|           if (c != '$') return false; | ||||
|           get_char(); | ||||
|         } else { | ||||
|           conv->width.set_from_arg(++*next_arg); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     if (c == '.') { | ||||
|       get_char(); | ||||
|       if (std::isdigit(c)) { | ||||
|         conv->precision.set_value(parse_digits()); | ||||
|       } else if (c == '*') { | ||||
|         get_char(); | ||||
|         if (is_positional) { | ||||
|           if (c < '1' || c > '9') return false; | ||||
|           conv->precision.set_from_arg(parse_digits()); | ||||
|           if (c != '$') return false; | ||||
|           get_char(); | ||||
|         } else { | ||||
|           conv->precision.set_from_arg(++*next_arg); | ||||
|         } | ||||
|       } else { | ||||
|         conv->precision.set_value(0); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   std::int8_t id = kIds[static_cast<unsigned char>(c)]; | ||||
| 
 | ||||
|   if (id < 0) { | ||||
|     if (id == none) return false; | ||||
| 
 | ||||
|     // It is a length modifier.
 | ||||
|     using str_format_internal::LengthMod; | ||||
|     LengthMod length_mod = LengthMod::FromId(static_cast<LM>(~id)); | ||||
|     get_char(); | ||||
|     if (c == 'h' && length_mod.id() == LengthMod::h) { | ||||
|       conv->length_mod = LengthMod::FromId(LengthMod::hh); | ||||
|       get_char(); | ||||
|     } else if (c == 'l' && length_mod.id() == LengthMod::l) { | ||||
|       conv->length_mod = LengthMod::FromId(LengthMod::ll); | ||||
|       get_char(); | ||||
|     } else { | ||||
|       conv->length_mod = length_mod; | ||||
|     } | ||||
|     id = kIds[static_cast<unsigned char>(c)]; | ||||
|     if (id < 0) return false; | ||||
|   } | ||||
| 
 | ||||
|   assert(CheckFastPathSetting(*conv)); | ||||
|   (void)(&CheckFastPathSetting); | ||||
| 
 | ||||
|   conv->conv = ConversionChar::FromId(static_cast<CC>(id)); | ||||
|   if (!is_positional) conv->arg_position = ++*next_arg; | ||||
|   *src = string_view(pos, end - pos); | ||||
|   return true; | ||||
| } | ||||
| 
 | ||||
| }  // namespace
 | ||||
| 
 | ||||
| bool ConsumeUnboundConversion(string_view *src, UnboundConversion *conv, | ||||
|                               int *next_arg) { | ||||
|   if (*next_arg < 0) return ConsumeConversion<true>(src, conv, next_arg); | ||||
|   return ConsumeConversion<false>(src, conv, next_arg); | ||||
| } | ||||
| 
 | ||||
| struct ParsedFormatBase::ParsedFormatConsumer { | ||||
|   explicit ParsedFormatConsumer(ParsedFormatBase *parsedformat) | ||||
|       : parsed(parsedformat), data_pos(parsedformat->data_.get()) {} | ||||
| 
 | ||||
|   bool Append(string_view s) { | ||||
|     if (s.empty()) return true; | ||||
| 
 | ||||
|     size_t text_end = AppendText(s); | ||||
| 
 | ||||
|     if (!parsed->items_.empty() && !parsed->items_.back().is_conversion) { | ||||
|       // Let's extend the existing text run.
 | ||||
|       parsed->items_.back().text_end = text_end; | ||||
|     } else { | ||||
|       // Let's make a new text run.
 | ||||
|       parsed->items_.push_back({false, text_end, {}}); | ||||
|     } | ||||
|     return true; | ||||
|   } | ||||
| 
 | ||||
|   bool ConvertOne(const UnboundConversion &conv, string_view s) { | ||||
|     size_t text_end = AppendText(s); | ||||
|     parsed->items_.push_back({true, text_end, conv}); | ||||
|     return true; | ||||
|   } | ||||
| 
 | ||||
|   size_t AppendText(string_view s) { | ||||
|     memcpy(data_pos, s.data(), s.size()); | ||||
|     data_pos += s.size(); | ||||
|     return static_cast<size_t>(data_pos - parsed->data_.get()); | ||||
|   } | ||||
| 
 | ||||
|   ParsedFormatBase *parsed; | ||||
|   char* data_pos; | ||||
| }; | ||||
| 
 | ||||
| ParsedFormatBase::ParsedFormatBase(string_view format, bool allow_ignored, | ||||
|                                    std::initializer_list<Conv> convs) | ||||
|     : data_(format.empty() ? nullptr : new char[format.size()]) { | ||||
|   has_error_ = !ParseFormatString(format, ParsedFormatConsumer(this)) || | ||||
|                !MatchesConversions(allow_ignored, convs); | ||||
| } | ||||
| 
 | ||||
| bool ParsedFormatBase::MatchesConversions( | ||||
|     bool allow_ignored, std::initializer_list<Conv> convs) const { | ||||
|   std::unordered_set<int> used; | ||||
|   auto add_if_valid_conv = [&](int pos, char c) { | ||||
|       if (static_cast<size_t>(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; | ||||
|     auto &conv = item.conv; | ||||
|     if (conv.precision.is_from_arg() && | ||||
|         !add_if_valid_conv(conv.precision.get_from_arg(), '*')) | ||||
|       return false; | ||||
|     if (conv.width.is_from_arg() && | ||||
|         !add_if_valid_conv(conv.width.get_from_arg(), '*')) | ||||
|       return false; | ||||
|     if (!add_if_valid_conv(conv.arg_position, conv.conv.Char())) return false; | ||||
|   } | ||||
|   return used.size() == convs.size() || allow_ignored; | ||||
| } | ||||
| 
 | ||||
| }  // namespace str_format_internal
 | ||||
| }  // namespace absl
 | ||||
							
								
								
									
										291
									
								
								absl/strings/internal/str_format/parser.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										291
									
								
								absl/strings/internal/str_format/parser.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,291 @@ | |||
| #ifndef ABSL_STRINGS_INTERNAL_STR_FORMAT_PARSER_H_ | ||||
| #define ABSL_STRINGS_INTERNAL_STR_FORMAT_PARSER_H_ | ||||
| 
 | ||||
| #include <limits.h> | ||||
| #include <stddef.h> | ||||
| #include <stdlib.h> | ||||
| 
 | ||||
| #include <cassert> | ||||
| #include <initializer_list> | ||||
| #include <iosfwd> | ||||
| #include <iterator> | ||||
| #include <memory> | ||||
| #include <vector> | ||||
| 
 | ||||
| #include "absl/strings/internal/str_format/checker.h" | ||||
| #include "absl/strings/internal/str_format/extension.h" | ||||
| 
 | ||||
| namespace absl { | ||||
| namespace str_format_internal { | ||||
| 
 | ||||
| // The analyzed properties of a single specified conversion.
 | ||||
| struct UnboundConversion { | ||||
|   UnboundConversion() | ||||
|       : flags() /* This is required to zero all the fields of flags. */ { | ||||
|     flags.basic = true; | ||||
|   } | ||||
| 
 | ||||
|   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; | ||||
|   LengthMod length_mod; | ||||
|   ConversionChar conv; | ||||
| }; | ||||
| 
 | ||||
| // Consume conversion spec prefix (not including '%') of '*src' if valid.
 | ||||
| // Examples of valid specs would be e.g.: "s", "d", "-12.6f".
 | ||||
| // If valid, the front of src is advanced such that src becomes the
 | ||||
| // part following the conversion spec, and the spec part is broken down and
 | ||||
| // returned in 'conv'.
 | ||||
| // If invalid, returns false and leaves 'src' unmodified.
 | ||||
| // For example:
 | ||||
| //   Given "d9", returns "d", and leaves src="9",
 | ||||
| //   Given "!", returns "" and leaves src="!".
 | ||||
| bool ConsumeUnboundConversion(string_view* src, UnboundConversion* conv, | ||||
|                               int* next_arg); | ||||
| 
 | ||||
| // Parse the format std::string provided in 'src' and pass the identified items into
 | ||||
| // 'consumer'.
 | ||||
| // Text runs will be passed by calling
 | ||||
| //   Consumer::Append(string_view);
 | ||||
| // ConversionItems will be passed by calling
 | ||||
| //   Consumer::ConvertOne(UnboundConversion, string_view);
 | ||||
| // In the case of ConvertOne, the string_view that is passed is the
 | ||||
| // portion of the format std::string corresponding to the conversion, not including
 | ||||
| // the leading %. On success, it returns true. On failure, it stops and returns
 | ||||
| // false.
 | ||||
| template <typename Consumer> | ||||
| bool ParseFormatString(string_view src, Consumer consumer) { | ||||
|   int next_arg = 0; | ||||
|   while (!src.empty()) { | ||||
|     const char* percent = | ||||
|         static_cast<const char*>(memchr(src.begin(), '%', src.size())); | ||||
|     if (!percent) { | ||||
|       // We found the last substring.
 | ||||
|       return consumer.Append(src); | ||||
|     } | ||||
|     // We found a percent, so push the text run then process the percent.
 | ||||
|     size_t percent_loc = percent - src.data(); | ||||
|     if (!consumer.Append(string_view(src.data(), percent_loc))) return false; | ||||
|     if (percent + 1 >= src.end()) return false; | ||||
| 
 | ||||
|     UnboundConversion conv; | ||||
| 
 | ||||
|     switch (percent[1]) { | ||||
|       case '%': | ||||
|         if (!consumer.Append("%")) return false; | ||||
|         src.remove_prefix(percent_loc + 2); | ||||
|         continue; | ||||
| 
 | ||||
| #define PARSER_CASE(ch)                                     \ | ||||
|   case #ch[0]:                                              \ | ||||
|     src.remove_prefix(percent_loc + 2);                     \ | ||||
|     conv.conv = ConversionChar::FromId(ConversionChar::ch); \ | ||||
|     conv.arg_position = ++next_arg;                         \ | ||||
|     break; | ||||
|         ABSL_CONVERSION_CHARS_EXPAND_(PARSER_CASE, ); | ||||
| #undef PARSER_CASE | ||||
| 
 | ||||
|       default: | ||||
|         src.remove_prefix(percent_loc + 1); | ||||
|         if (!ConsumeUnboundConversion(&src, &conv, &next_arg)) return false; | ||||
|         break; | ||||
|     } | ||||
|     if (next_arg == 0) { | ||||
|       // This indicates an error in the format std::string.
 | ||||
|       // The only way to get next_arg == 0 is to have a positional argument
 | ||||
|       // first which sets next_arg to -1 and then a non-positional argument
 | ||||
|       // which does ++next_arg.
 | ||||
|       // Checking here seems to be the cheapeast place to do it.
 | ||||
|       return false; | ||||
|     } | ||||
|     if (!consumer.ConvertOne( | ||||
|             conv, string_view(percent + 1, src.data() - (percent + 1)))) { | ||||
|       return false; | ||||
|     } | ||||
|   } | ||||
|   return true; | ||||
| } | ||||
| 
 | ||||
| // Always returns true, or fails to compile in a constexpr context if s does not
 | ||||
| // point to a constexpr char array.
 | ||||
| constexpr bool EnsureConstexpr(string_view s) { | ||||
|   return s.empty() || s[0] == s[0]; | ||||
| } | ||||
| 
 | ||||
| class ParsedFormatBase { | ||||
|  public: | ||||
|   explicit ParsedFormatBase(string_view format, bool allow_ignored, | ||||
|                             std::initializer_list<Conv> convs); | ||||
| 
 | ||||
|   ParsedFormatBase(const ParsedFormatBase& other) { *this = other; } | ||||
| 
 | ||||
|   ParsedFormatBase(ParsedFormatBase&& other) { *this = std::move(other); } | ||||
| 
 | ||||
|   ParsedFormatBase& operator=(const ParsedFormatBase& other) { | ||||
|     if (this == &other) return *this; | ||||
|     has_error_ = other.has_error_; | ||||
|     items_ = other.items_; | ||||
|     size_t text_size = items_.empty() ? 0 : items_.back().text_end; | ||||
|     data_.reset(new char[text_size]); | ||||
|     memcpy(data_.get(), other.data_.get(), text_size); | ||||
|     return *this; | ||||
|   } | ||||
| 
 | ||||
|   ParsedFormatBase& operator=(ParsedFormatBase&& other) { | ||||
|     if (this == &other) return *this; | ||||
|     has_error_ = other.has_error_; | ||||
|     data_ = std::move(other.data_); | ||||
|     items_ = std::move(other.items_); | ||||
|     // Reset the vector to make sure the invariants hold.
 | ||||
|     other.items_.clear(); | ||||
|     return *this; | ||||
|   } | ||||
| 
 | ||||
|   template <typename Consumer> | ||||
|   bool ProcessFormat(Consumer consumer) const { | ||||
|     const char* const base = data_.get(); | ||||
|     string_view text(base, 0); | ||||
|     for (const auto& item : items_) { | ||||
|       text = string_view(text.end(), (base + item.text_end) - text.end()); | ||||
|       if (item.is_conversion) { | ||||
|         if (!consumer.ConvertOne(item.conv, text)) return false; | ||||
|       } else { | ||||
|         if (!consumer.Append(text)) return false; | ||||
|       } | ||||
|     } | ||||
|     return !has_error_; | ||||
|   } | ||||
| 
 | ||||
|   bool has_error() const { return has_error_; } | ||||
| 
 | ||||
|  private: | ||||
|   // Returns whether the conversions match and if !allow_ignored it verifies
 | ||||
|   // that all conversions are used by the format.
 | ||||
|   bool MatchesConversions(bool allow_ignored, | ||||
|                           std::initializer_list<Conv> convs) const; | ||||
| 
 | ||||
|   struct ParsedFormatConsumer; | ||||
| 
 | ||||
|   struct ConversionItem { | ||||
|     bool is_conversion; | ||||
|     // Points to the past-the-end location of this element in the data_ array.
 | ||||
|     size_t text_end; | ||||
|     UnboundConversion conv; | ||||
|   }; | ||||
| 
 | ||||
|   bool has_error_; | ||||
|   std::unique_ptr<char[]> data_; | ||||
|   std::vector<ConversionItem> items_; | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| // A value type representing a preparsed format.  These can be created, copied
 | ||||
| // around, and reused to speed up formatting loops.
 | ||||
| // The user must specify through the template arguments the conversion
 | ||||
| // characters used in the format. This will be checked at compile time.
 | ||||
| //
 | ||||
| // This class uses Conv enum values to specify each argument.
 | ||||
| // This allows for more flexibility as you can specify multiple possible
 | ||||
| // conversion characters for each argument.
 | ||||
| // ParsedFormat<char...> is a simplified alias for when the user only
 | ||||
| // needs to specify a single conversion character for each argument.
 | ||||
| //
 | ||||
| // Example:
 | ||||
| //   // Extended format supports multiple characters per argument:
 | ||||
| //   using MyFormat = ExtendedParsedFormat<Conv::d | Conv::x>;
 | ||||
| //   MyFormat GetFormat(bool use_hex) {
 | ||||
| //     if (use_hex) return MyFormat("foo %x bar");
 | ||||
| //     return MyFormat("foo %d bar");
 | ||||
| //   }
 | ||||
| //   // 'format' can be used with any value that supports 'd' and 'x',
 | ||||
| //   // like `int`.
 | ||||
| //   auto format = GetFormat(use_hex);
 | ||||
| //   value = StringF(format, i);
 | ||||
| //
 | ||||
| // This class also supports runtime format checking with the ::New() and
 | ||||
| // ::NewAllowIgnored() factory functions.
 | ||||
| // This is the only API that allows the user to pass a runtime specified format
 | ||||
| // std::string. These factory functions will return NULL if the format does not match
 | ||||
| // the conversions requested by the user.
 | ||||
| template <str_format_internal::Conv... C> | ||||
| class ExtendedParsedFormat : public str_format_internal::ParsedFormatBase { | ||||
|  public: | ||||
|   explicit ExtendedParsedFormat(string_view format) | ||||
| #if ABSL_INTERNAL_ENABLE_FORMAT_CHECKER | ||||
|       __attribute__(( | ||||
|           enable_if(str_format_internal::EnsureConstexpr(format), | ||||
|                     "Format std::string is not constexpr."), | ||||
|           enable_if(str_format_internal::ValidFormatImpl<C...>(format), | ||||
|                     "Format specified does not match the template arguments."))) | ||||
| #endif  // ABSL_INTERNAL_ENABLE_FORMAT_CHECKER
 | ||||
|       : ExtendedParsedFormat(format, false) { | ||||
|   } | ||||
| 
 | ||||
|   // ExtendedParsedFormat factory function.
 | ||||
|   // The user still has to specify the conversion characters, but they will not
 | ||||
|   // be checked at compile time. Instead, it will be checked at runtime.
 | ||||
|   // This delays the checking to runtime, but allows the user to pass
 | ||||
|   // dynamically sourced formats.
 | ||||
|   // It returns NULL if the format does not match the conversion characters.
 | ||||
|   // The user is responsible for checking the return value before using it.
 | ||||
|   //
 | ||||
|   // The 'New' variant will check that all the specified arguments are being
 | ||||
|   // consumed by the format and return NULL if any argument is being ignored.
 | ||||
|   // The 'NewAllowIgnored' variant will not verify this and will allow formats
 | ||||
|   // that ignore arguments.
 | ||||
|   static std::unique_ptr<ExtendedParsedFormat> New(string_view format) { | ||||
|     return New(format, false); | ||||
|   } | ||||
|   static std::unique_ptr<ExtendedParsedFormat> NewAllowIgnored( | ||||
|       string_view format) { | ||||
|     return New(format, true); | ||||
|   } | ||||
| 
 | ||||
|  private: | ||||
|   static std::unique_ptr<ExtendedParsedFormat> New(string_view format, | ||||
|                                                    bool allow_ignored) { | ||||
|     std::unique_ptr<ExtendedParsedFormat> conv( | ||||
|         new ExtendedParsedFormat(format, allow_ignored)); | ||||
|     if (conv->has_error()) return nullptr; | ||||
|     return conv; | ||||
|   } | ||||
| 
 | ||||
|   ExtendedParsedFormat(string_view s, bool allow_ignored) | ||||
|       : ParsedFormatBase(s, allow_ignored, {C...}) {} | ||||
| }; | ||||
| }  // namespace str_format_internal
 | ||||
| }  // namespace absl
 | ||||
| 
 | ||||
| #endif  // ABSL_STRINGS_INTERNAL_STR_FORMAT_PARSER_H_
 | ||||
							
								
								
									
										379
									
								
								absl/strings/internal/str_format/parser_test.cc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										379
									
								
								absl/strings/internal/str_format/parser_test.cc
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,379 @@ | |||
| #include "absl/strings/internal/str_format/parser.h" | ||||
| 
 | ||||
| #include <string.h> | ||||
| #include "gtest/gtest.h" | ||||
| #include "absl/base/macros.h" | ||||
| 
 | ||||
| namespace absl { | ||||
| namespace str_format_internal { | ||||
| 
 | ||||
| namespace { | ||||
| 
 | ||||
| TEST(LengthModTest, Names) { | ||||
|   struct Expectation { | ||||
|     int line; | ||||
|     LengthMod::Id id; | ||||
|     const char *name; | ||||
|   }; | ||||
|   const Expectation kExpect[] = { | ||||
|     {__LINE__, LengthMod::none, ""  }, | ||||
|     {__LINE__, LengthMod::h,    "h" }, | ||||
|     {__LINE__, LengthMod::hh,   "hh"}, | ||||
|     {__LINE__, LengthMod::l,    "l" }, | ||||
|     {__LINE__, LengthMod::ll,   "ll"}, | ||||
|     {__LINE__, LengthMod::L,    "L" }, | ||||
|     {__LINE__, LengthMod::j,    "j" }, | ||||
|     {__LINE__, LengthMod::z,    "z" }, | ||||
|     {__LINE__, LengthMod::t,    "t" }, | ||||
|     {__LINE__, LengthMod::q,    "q" }, | ||||
|   }; | ||||
|   EXPECT_EQ(ABSL_ARRAYSIZE(kExpect), LengthMod::kNumValues); | ||||
|   for (auto e : kExpect) { | ||||
|     SCOPED_TRACE(e.line); | ||||
|     LengthMod mod = LengthMod::FromId(e.id); | ||||
|     EXPECT_EQ(e.id, mod.id()); | ||||
|     EXPECT_EQ(e.name, mod.name()); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| TEST(ConversionCharTest, Names) { | ||||
|   struct Expectation { | ||||
|     ConversionChar::Id id; | ||||
|     char name; | ||||
|   }; | ||||
|   // clang-format off
 | ||||
|   const Expectation kExpect[] = { | ||||
| #define X(c) {ConversionChar::c, #c[0]} | ||||
|     X(c), X(C), X(s), X(S),                          // text
 | ||||
|     X(d), X(i), X(o), X(u), X(x), X(X),              // int
 | ||||
|     X(f), X(F), X(e), X(E), X(g), X(G), X(a), X(A),  // float
 | ||||
|     X(n), X(p),                                      // misc
 | ||||
| #undef X | ||||
|     {ConversionChar::none, '\0'}, | ||||
|   }; | ||||
|   // clang-format on
 | ||||
|   EXPECT_EQ(ABSL_ARRAYSIZE(kExpect), ConversionChar::kNumValues); | ||||
|   for (auto e : kExpect) { | ||||
|     SCOPED_TRACE(e.name); | ||||
|     ConversionChar v = ConversionChar::FromId(e.id); | ||||
|     EXPECT_EQ(e.id, v.id()); | ||||
|     EXPECT_EQ(e.name, v.Char()); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| class ConsumeUnboundConversionTest : public ::testing::Test { | ||||
|  public: | ||||
|   typedef UnboundConversion Props; | ||||
|   string_view Consume(string_view* src) { | ||||
|     int next = 0; | ||||
|     const char* prev_begin = src->begin(); | ||||
|     o = UnboundConversion();  // refresh
 | ||||
|     ConsumeUnboundConversion(src, &o, &next); | ||||
|     return {prev_begin, static_cast<size_t>(src->begin() - prev_begin)}; | ||||
|   } | ||||
| 
 | ||||
|   bool Run(const char *fmt, bool force_positional = false) { | ||||
|     string_view src = fmt; | ||||
|     int next = force_positional ? -1 : 0; | ||||
|     o = UnboundConversion();  // refresh
 | ||||
|     return ConsumeUnboundConversion(&src, &o, &next) && src.empty(); | ||||
|   } | ||||
|   UnboundConversion o; | ||||
| }; | ||||
| 
 | ||||
| TEST_F(ConsumeUnboundConversionTest, ConsumeSpecification) { | ||||
|   struct Expectation { | ||||
|     int line; | ||||
|     const char *src; | ||||
|     const char *out; | ||||
|     const char *src_post; | ||||
|   }; | ||||
|   const Expectation kExpect[] = { | ||||
|     {__LINE__, "",     "",     ""  }, | ||||
|     {__LINE__, "b",    "",     "b" },  // 'b' is invalid
 | ||||
|     {__LINE__, "ba",   "",     "ba"},  // 'b' is invalid
 | ||||
|     {__LINE__, "l",    "",     "l" },  // just length mod isn't okay
 | ||||
|     {__LINE__, "d",    "d",    ""  },  // basic
 | ||||
|     {__LINE__, "d ",   "d",    " " },  // leave suffix
 | ||||
|     {__LINE__, "dd",   "d",    "d" },  // don't be greedy
 | ||||
|     {__LINE__, "d9",   "d",    "9" },  // leave non-space suffix
 | ||||
|     {__LINE__, "dzz",  "d",    "zz"},  // length mod as suffix
 | ||||
|     {__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
 | ||||
|   }; | ||||
|   for (const auto& e : kExpect) { | ||||
|     SCOPED_TRACE(e.line); | ||||
|     string_view src = e.src; | ||||
|     EXPECT_EQ(e.src, src); | ||||
|     string_view out = Consume(&src); | ||||
|     EXPECT_EQ(e.out, out); | ||||
|     EXPECT_EQ(e.src_post, src); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| TEST_F(ConsumeUnboundConversionTest, BasicConversion) { | ||||
|   EXPECT_FALSE(Run("")); | ||||
|   EXPECT_FALSE(Run("z")); | ||||
| 
 | ||||
|   EXPECT_FALSE(Run("dd"));  // no excess allowed
 | ||||
| 
 | ||||
|   EXPECT_TRUE(Run("d")); | ||||
|   EXPECT_EQ('d', o.conv.Char()); | ||||
|   EXPECT_FALSE(o.width.is_from_arg()); | ||||
|   EXPECT_LT(o.width.value(), 0); | ||||
|   EXPECT_FALSE(o.precision.is_from_arg()); | ||||
|   EXPECT_LT(o.precision.value(), 0); | ||||
|   EXPECT_EQ(1, o.arg_position); | ||||
|   EXPECT_EQ(LengthMod::none, o.length_mod.id()); | ||||
| } | ||||
| 
 | ||||
| TEST_F(ConsumeUnboundConversionTest, ArgPosition) { | ||||
|   EXPECT_TRUE(Run("d")); | ||||
|   EXPECT_EQ(1, o.arg_position); | ||||
|   EXPECT_TRUE(Run("3$d")); | ||||
|   EXPECT_EQ(3, o.arg_position); | ||||
|   EXPECT_TRUE(Run("1$d")); | ||||
|   EXPECT_EQ(1, o.arg_position); | ||||
|   EXPECT_TRUE(Run("1$d", true)); | ||||
|   EXPECT_EQ(1, o.arg_position); | ||||
|   EXPECT_TRUE(Run("123$d")); | ||||
|   EXPECT_EQ(123, o.arg_position); | ||||
|   EXPECT_TRUE(Run("123$d", true)); | ||||
|   EXPECT_EQ(123, o.arg_position); | ||||
|   EXPECT_TRUE(Run("10$d")); | ||||
|   EXPECT_EQ(10, o.arg_position); | ||||
|   EXPECT_TRUE(Run("10$d", true)); | ||||
|   EXPECT_EQ(10, o.arg_position); | ||||
| 
 | ||||
|   // Position can't be zero.
 | ||||
|   EXPECT_FALSE(Run("0$d")); | ||||
|   EXPECT_FALSE(Run("0$d", true)); | ||||
|   EXPECT_FALSE(Run("1$*0$d")); | ||||
|   EXPECT_FALSE(Run("1$.*0$d")); | ||||
| 
 | ||||
|   // Position can't start with a zero digit at all. That is not a 'decimal'.
 | ||||
|   EXPECT_FALSE(Run("01$p")); | ||||
|   EXPECT_FALSE(Run("01$p", true)); | ||||
|   EXPECT_FALSE(Run("1$*01$p")); | ||||
|   EXPECT_FALSE(Run("1$.*01$p")); | ||||
| } | ||||
| 
 | ||||
| TEST_F(ConsumeUnboundConversionTest, WidthAndPrecision) { | ||||
|   EXPECT_TRUE(Run("14d")); | ||||
|   EXPECT_EQ('d', o.conv.Char()); | ||||
|   EXPECT_FALSE(o.width.is_from_arg()); | ||||
|   EXPECT_EQ(14, o.width.value()); | ||||
|   EXPECT_FALSE(o.precision.is_from_arg()); | ||||
|   EXPECT_LT(o.precision.value(), 0); | ||||
| 
 | ||||
|   EXPECT_TRUE(Run("14.d")); | ||||
|   EXPECT_FALSE(o.width.is_from_arg()); | ||||
|   EXPECT_FALSE(o.precision.is_from_arg()); | ||||
|   EXPECT_EQ(14, o.width.value()); | ||||
|   EXPECT_EQ(0, o.precision.value()); | ||||
| 
 | ||||
|   EXPECT_TRUE(Run(".d")); | ||||
|   EXPECT_FALSE(o.width.is_from_arg()); | ||||
|   EXPECT_LT(o.width.value(), 0); | ||||
|   EXPECT_FALSE(o.precision.is_from_arg()); | ||||
|   EXPECT_EQ(0, o.precision.value()); | ||||
| 
 | ||||
|   EXPECT_TRUE(Run(".5d")); | ||||
|   EXPECT_FALSE(o.width.is_from_arg()); | ||||
|   EXPECT_LT(o.width.value(), 0); | ||||
|   EXPECT_FALSE(o.precision.is_from_arg()); | ||||
|   EXPECT_EQ(5, o.precision.value()); | ||||
| 
 | ||||
|   EXPECT_TRUE(Run(".0d")); | ||||
|   EXPECT_FALSE(o.width.is_from_arg()); | ||||
|   EXPECT_LT(o.width.value(), 0); | ||||
|   EXPECT_FALSE(o.precision.is_from_arg()); | ||||
|   EXPECT_EQ(0, o.precision.value()); | ||||
| 
 | ||||
|   EXPECT_TRUE(Run("14.5d")); | ||||
|   EXPECT_FALSE(o.width.is_from_arg()); | ||||
|   EXPECT_FALSE(o.precision.is_from_arg()); | ||||
|   EXPECT_EQ(14, o.width.value()); | ||||
|   EXPECT_EQ(5, o.precision.value()); | ||||
| 
 | ||||
|   EXPECT_TRUE(Run("*.*d")); | ||||
|   EXPECT_TRUE(o.width.is_from_arg()); | ||||
|   EXPECT_EQ(1, o.width.get_from_arg()); | ||||
|   EXPECT_TRUE(o.precision.is_from_arg()); | ||||
|   EXPECT_EQ(2, o.precision.get_from_arg()); | ||||
|   EXPECT_EQ(3, o.arg_position); | ||||
| 
 | ||||
|   EXPECT_TRUE(Run("*d")); | ||||
|   EXPECT_TRUE(o.width.is_from_arg()); | ||||
|   EXPECT_EQ(1, o.width.get_from_arg()); | ||||
|   EXPECT_FALSE(o.precision.is_from_arg()); | ||||
|   EXPECT_LT(o.precision.value(), 0); | ||||
|   EXPECT_EQ(2, o.arg_position); | ||||
| 
 | ||||
|   EXPECT_TRUE(Run(".*d")); | ||||
|   EXPECT_FALSE(o.width.is_from_arg()); | ||||
|   EXPECT_LT(o.width.value(), 0); | ||||
|   EXPECT_TRUE(o.precision.is_from_arg()); | ||||
|   EXPECT_EQ(1, o.precision.get_from_arg()); | ||||
|   EXPECT_EQ(2, o.arg_position); | ||||
| 
 | ||||
|   // mixed implicit and explicit: didn't specify arg position.
 | ||||
|   EXPECT_FALSE(Run("*23$.*34$d")); | ||||
| 
 | ||||
|   EXPECT_TRUE(Run("12$*23$.*34$d")); | ||||
|   EXPECT_EQ(12, o.arg_position); | ||||
|   EXPECT_TRUE(o.width.is_from_arg()); | ||||
|   EXPECT_EQ(23, o.width.get_from_arg()); | ||||
|   EXPECT_TRUE(o.precision.is_from_arg()); | ||||
|   EXPECT_EQ(34, o.precision.get_from_arg()); | ||||
| 
 | ||||
|   EXPECT_TRUE(Run("2$*5$.*9$d")); | ||||
|   EXPECT_EQ(2, o.arg_position); | ||||
|   EXPECT_TRUE(o.width.is_from_arg()); | ||||
|   EXPECT_EQ(5, o.width.get_from_arg()); | ||||
|   EXPECT_TRUE(o.precision.is_from_arg()); | ||||
|   EXPECT_EQ(9, o.precision.get_from_arg()); | ||||
| 
 | ||||
|   EXPECT_FALSE(Run(".*0$d")) << "no arg 0"; | ||||
| } | ||||
| 
 | ||||
| TEST_F(ConsumeUnboundConversionTest, Flags) { | ||||
|   static const char kAllFlags[] = "-+ #0"; | ||||
|   static const int kNumFlags = ABSL_ARRAYSIZE(kAllFlags) - 1; | ||||
|   for (int rev = 0; rev < 2; ++rev) { | ||||
|     for (int i = 0; i < 1 << kNumFlags; ++i) { | ||||
|       std::string fmt; | ||||
|       for (int k = 0; k < kNumFlags; ++k) | ||||
|         if ((i >> k) & 1) fmt += kAllFlags[k]; | ||||
|       // flag order shouldn't matter
 | ||||
|       if (rev == 1) { std::reverse(fmt.begin(), fmt.end()); } | ||||
|       fmt += 'd'; | ||||
|       SCOPED_TRACE(fmt); | ||||
|       EXPECT_TRUE(Run(fmt.c_str())); | ||||
|       EXPECT_EQ(fmt.find('-') == std::string::npos, !o.flags.left); | ||||
|       EXPECT_EQ(fmt.find('+') == std::string::npos, !o.flags.show_pos); | ||||
|       EXPECT_EQ(fmt.find(' ') == std::string::npos, !o.flags.sign_col); | ||||
|       EXPECT_EQ(fmt.find('#') == std::string::npos, !o.flags.alt); | ||||
|       EXPECT_EQ(fmt.find('0') == std::string::npos, !o.flags.zero); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| TEST_F(ConsumeUnboundConversionTest, BasicFlag) { | ||||
|   // Flag is on
 | ||||
|   for (const char* fmt : {"d", "llx", "G", "1$X"}) { | ||||
|     SCOPED_TRACE(fmt); | ||||
|     EXPECT_TRUE(Run(fmt)); | ||||
|     EXPECT_TRUE(o.flags.basic); | ||||
|   } | ||||
| 
 | ||||
|   // Flag is off
 | ||||
|   for (const char* fmt : {"3d", ".llx", "-G", "1$#X"}) { | ||||
|     SCOPED_TRACE(fmt); | ||||
|     EXPECT_TRUE(Run(fmt)); | ||||
|     EXPECT_FALSE(o.flags.basic); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| struct SummarizeConsumer { | ||||
|   std::string* out; | ||||
|   explicit SummarizeConsumer(std::string* out) : out(out) {} | ||||
| 
 | ||||
|   bool Append(string_view s) { | ||||
|     *out += "[" + std::string(s) + "]"; | ||||
|     return true; | ||||
|   } | ||||
| 
 | ||||
|   bool ConvertOne(const UnboundConversion& conv, string_view s) { | ||||
|     *out += "{"; | ||||
|     *out += std::string(s); | ||||
|     *out += ":"; | ||||
|     *out += std::to_string(conv.arg_position) + "$"; | ||||
|     if (conv.width.is_from_arg()) { | ||||
|       *out += std::to_string(conv.width.get_from_arg()) + "$*"; | ||||
|     } | ||||
|     if (conv.precision.is_from_arg()) { | ||||
|       *out += "." + std::to_string(conv.precision.get_from_arg()) + "$*"; | ||||
|     } | ||||
|     *out += conv.conv.Char(); | ||||
|     *out += "}"; | ||||
|     return true; | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| std::string SummarizeParsedFormat(const ParsedFormatBase& pc) { | ||||
|   std::string out; | ||||
|   if (!pc.ProcessFormat(SummarizeConsumer(&out))) out += "!"; | ||||
|   return out; | ||||
| } | ||||
| 
 | ||||
| class ParsedFormatTest : public testing::Test {}; | ||||
| 
 | ||||
| TEST_F(ParsedFormatTest, ValueSemantics) { | ||||
|   ParsedFormatBase p1({}, true, {});  // empty format
 | ||||
|   EXPECT_EQ("", SummarizeParsedFormat(p1)); | ||||
| 
 | ||||
|   ParsedFormatBase p2 = p1;  // copy construct (empty)
 | ||||
|   EXPECT_EQ(SummarizeParsedFormat(p1), SummarizeParsedFormat(p2)); | ||||
| 
 | ||||
|   p1 = ParsedFormatBase("hello%s", true, {Conv::s});  // move assign
 | ||||
|   EXPECT_EQ("[hello]{s:1$s}", SummarizeParsedFormat(p1)); | ||||
| 
 | ||||
|   ParsedFormatBase p3 = p1;  // copy construct (nonempty)
 | ||||
|   EXPECT_EQ(SummarizeParsedFormat(p1), SummarizeParsedFormat(p3)); | ||||
| 
 | ||||
|   using std::swap; | ||||
|   swap(p1, p2); | ||||
|   EXPECT_EQ("", SummarizeParsedFormat(p1)); | ||||
|   EXPECT_EQ("[hello]{s:1$s}", SummarizeParsedFormat(p2)); | ||||
|   swap(p1, p2);  // undo
 | ||||
| 
 | ||||
|   p2 = p1;  // copy assign
 | ||||
|   EXPECT_EQ(SummarizeParsedFormat(p1), SummarizeParsedFormat(p2)); | ||||
| } | ||||
| 
 | ||||
| struct ExpectParse { | ||||
|   const char* in; | ||||
|   std::initializer_list<Conv> conv_set; | ||||
|   const char* out; | ||||
| }; | ||||
| 
 | ||||
| TEST_F(ParsedFormatTest, Parsing) { | ||||
|   // Parse should be equivalent to that obtained by ConversionParseIterator.
 | ||||
|   // No need to retest the parsing edge cases here.
 | ||||
|   const ExpectParse kExpect[] = { | ||||
|       {"", {}, ""}, | ||||
|       {"ab", {}, "[ab]"}, | ||||
|       {"a%d", {Conv::d}, "[a]{d:1$d}"}, | ||||
|       {"a%+d", {Conv::d}, "[a]{+d:1$d}"}, | ||||
|       {"a% d", {Conv::d}, "[a]{ d:1$d}"}, | ||||
|       {"a%b %d", {}, "[a]!"},  // stop after error
 | ||||
|   }; | ||||
|   for (const auto& e : kExpect) { | ||||
|     SCOPED_TRACE(e.in); | ||||
|     EXPECT_EQ(e.out, | ||||
|               SummarizeParsedFormat(ParsedFormatBase(e.in, false, e.conv_set))); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| TEST_F(ParsedFormatTest, ParsingFlagOrder) { | ||||
|   const ExpectParse kExpect[] = { | ||||
|       {"a%+ 0d", {Conv::d}, "[a]{+ 0d:1$d}"}, | ||||
|       {"a%+0 d", {Conv::d}, "[a]{+0 d:1$d}"}, | ||||
|       {"a%0+ d", {Conv::d}, "[a]{0+ d:1$d}"}, | ||||
|       {"a% +0d", {Conv::d}, "[a]{ +0d:1$d}"}, | ||||
|       {"a%0 +d", {Conv::d}, "[a]{0 +d:1$d}"}, | ||||
|       {"a% 0+d", {Conv::d}, "[a]{ 0+d:1$d}"}, | ||||
|       {"a%+   0+d", {Conv::d}, "[a]{+   0+d:1$d}"}, | ||||
|   }; | ||||
|   for (const auto& e : kExpect) { | ||||
|     SCOPED_TRACE(e.in); | ||||
|     EXPECT_EQ(e.out, | ||||
|               SummarizeParsedFormat(ParsedFormatBase(e.in, false, e.conv_set))); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| }  // namespace
 | ||||
| }  // namespace str_format_internal
 | ||||
| }  // namespace absl
 | ||||
							
								
								
									
										512
									
								
								absl/strings/str_format.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										512
									
								
								absl/strings/str_format.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,512 @@ | |||
| //
 | ||||
| // Copyright 2018 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
 | ||||
| //
 | ||||
| //      http://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.
 | ||||
| //
 | ||||
| // -----------------------------------------------------------------------------
 | ||||
| // File: str_format.h
 | ||||
| // -----------------------------------------------------------------------------
 | ||||
| //
 | ||||
| // The `str_format` library is a typesafe replacement for the family of
 | ||||
| // `printf()` std::string formatting routines within the `<cstdio>` standard library
 | ||||
| // header. Like the `printf` family, the `str_format` uses a "format string" to
 | ||||
| // perform argument substitutions based on types.
 | ||||
| //
 | ||||
| // Example:
 | ||||
| //
 | ||||
| //   std::string s = absl::StrFormat("%s %s You have $%d!", "Hello", name, dollars);
 | ||||
| //
 | ||||
| // The library consists of the following basic utilities:
 | ||||
| //
 | ||||
| //   * `absl::StrFormat()`, a type-safe replacement for `std::sprintf()`, to
 | ||||
| //     write a format std::string to a `string` value.
 | ||||
| //   * `absl::StrAppendFormat()` to append a format std::string to a `string`
 | ||||
| //   * `absl::StreamFormat()` to more efficiently write a format std::string to a
 | ||||
| //     stream, such as`std::cout`.
 | ||||
| //   * `absl::PrintF()`, `absl::FPrintF()` and `absl::SNPrintF()` as
 | ||||
| //     replacements for `std::printf()`, `std::fprintf()` and `std::snprintf()`.
 | ||||
| //
 | ||||
| //     Note: a version of `std::sprintf()` is not supported as it is
 | ||||
| //     generally unsafe due to buffer overflows.
 | ||||
| //
 | ||||
| // Additionally, you can provide a format std::string (and its associated arguments)
 | ||||
| // using one of the following abstractions:
 | ||||
| //
 | ||||
| //   * A `FormatSpec` class template fully encapsulates a format std::string and its
 | ||||
| //     type arguments and is usually provided to `str_format` functions as a
 | ||||
| //     variadic argument of type `FormatSpec<Arg...>`. The `FormatSpec<Args...>`
 | ||||
| //     template is evaluated at compile-time, providing type safety.
 | ||||
| //   * A `ParsedFormat` instance, which encapsulates a specific, pre-compiled
 | ||||
| //     format std::string for a specific set of type(s), and which can be passed
 | ||||
| //     between API boundaries. (The `FormatSpec` type should not be used
 | ||||
| //     directly.)
 | ||||
| //
 | ||||
| // The `str_format` library provides the ability to output its format strings to
 | ||||
| // arbitrary sink types:
 | ||||
| //
 | ||||
| //   * A generic `Format()` function to write outputs to arbitrary sink types,
 | ||||
| //     which must implement a `RawSinkFormat` interface. (See
 | ||||
| //     `str_format_sink.h` for more information.)
 | ||||
| //
 | ||||
| //   * A `FormatUntyped()` function that is similar to `Format()` except it is
 | ||||
| //     loosely typed. `FormatUntyped()` is not a template and does not perform
 | ||||
| //     any compile-time checking of the format std::string; instead, it returns a
 | ||||
| //     boolean from a runtime check.
 | ||||
| //
 | ||||
| // In addition, the `str_format` library provides extension points for
 | ||||
| // augmenting formatting to new types. These extensions are fully documented
 | ||||
| // within the `str_format_extension.h` header file.
 | ||||
| #ifndef ABSL_STRINGS_STR_FORMAT_H_ | ||||
| #define ABSL_STRINGS_STR_FORMAT_H_ | ||||
| 
 | ||||
| #include <cstdio> | ||||
| #include <string> | ||||
| 
 | ||||
| #include "absl/strings/internal/str_format/arg.h"  // IWYU pragma: export | ||||
| #include "absl/strings/internal/str_format/bind.h"  // IWYU pragma: export | ||||
| #include "absl/strings/internal/str_format/checker.h"  // IWYU pragma: export | ||||
| #include "absl/strings/internal/str_format/extension.h"  // IWYU pragma: export | ||||
| #include "absl/strings/internal/str_format/parser.h"  // IWYU pragma: export | ||||
| 
 | ||||
| namespace absl { | ||||
| 
 | ||||
| // UntypedFormatSpec
 | ||||
| //
 | ||||
| // A type-erased class that can be used directly within untyped API entry
 | ||||
| // points. An `UntypedFormatSpec` is specifically used as an argument to
 | ||||
| // `FormatUntyped()`.
 | ||||
| //
 | ||||
| // Example:
 | ||||
| //
 | ||||
| //   absl::UntypedFormatSpec format("%d");
 | ||||
| //   std::string out;
 | ||||
| //   CHECK(absl::FormatUntyped(&out, format, {absl::FormatArg(1)}));
 | ||||
| class UntypedFormatSpec { | ||||
|  public: | ||||
|   UntypedFormatSpec() = delete; | ||||
|   UntypedFormatSpec(const UntypedFormatSpec&) = delete; | ||||
|   UntypedFormatSpec& operator=(const UntypedFormatSpec&) = delete; | ||||
| 
 | ||||
|   explicit UntypedFormatSpec(string_view s) : spec_(s) {} | ||||
| 
 | ||||
|  protected: | ||||
|   explicit UntypedFormatSpec(const str_format_internal::ParsedFormatBase* pc) | ||||
|       : spec_(pc) {} | ||||
| 
 | ||||
|  private: | ||||
|   friend str_format_internal::UntypedFormatSpecImpl; | ||||
|   str_format_internal::UntypedFormatSpecImpl spec_; | ||||
| }; | ||||
| 
 | ||||
| // FormatStreamed()
 | ||||
| //
 | ||||
| // Takes a streamable argument and returns an object that can print it
 | ||||
| // with '%s'. Allows printing of types that have an `operator<<` but no
 | ||||
| // intrinsic type support within `StrFormat()` itself.
 | ||||
| //
 | ||||
| // Example:
 | ||||
| //
 | ||||
| //   absl::StrFormat("%s", absl::FormatStreamed(obj));
 | ||||
| template <typename T> | ||||
| str_format_internal::StreamedWrapper<T> FormatStreamed(const T& v) { | ||||
|   return str_format_internal::StreamedWrapper<T>(v); | ||||
| } | ||||
| 
 | ||||
| // FormatCountCapture
 | ||||
| //
 | ||||
| // This class provides a way to safely wrap `StrFormat()` captures of `%n`
 | ||||
| // conversions, which denote the number of characters written by a formatting
 | ||||
| // operation to this point, into an integer value.
 | ||||
| //
 | ||||
| // This wrapper is designed to allow safe usage of `%n` within `StrFormat(); in
 | ||||
| // the `printf()` family of functions, `%n` is not safe to use, as the `int *`
 | ||||
| // buffer can be used to capture arbitrary data.
 | ||||
| //
 | ||||
| // Example:
 | ||||
| //
 | ||||
| //   int n = 0;
 | ||||
| //   std::string s = absl::StrFormat("%s%d%n", "hello", 123,
 | ||||
| //                   absl::FormatCountCapture(&n));
 | ||||
| //   EXPECT_EQ(8, n);
 | ||||
| class FormatCountCapture { | ||||
|  public: | ||||
|   explicit FormatCountCapture(int* p) : p_(p) {} | ||||
| 
 | ||||
|  private: | ||||
|   // FormatCountCaptureHelper is used to define FormatConvertImpl() for this
 | ||||
|   // class.
 | ||||
|   friend struct str_format_internal::FormatCountCaptureHelper; | ||||
|   // Unused() is here because of the false positive from -Wunused-private-field
 | ||||
|   // p_ is used in the templated function of the friend FormatCountCaptureHelper
 | ||||
|   // class.
 | ||||
|   int* Unused() { return p_; } | ||||
|   int* p_; | ||||
| }; | ||||
| 
 | ||||
| // FormatSpec
 | ||||
| //
 | ||||
| // The `FormatSpec` type defines the makeup of a format std::string within the
 | ||||
| // `str_format` library. You should not need to use or manipulate this type
 | ||||
| // directly. A `FormatSpec` is a variadic class template that is evaluated at
 | ||||
| // compile-time, according to the format std::string and arguments that are passed
 | ||||
| // to it.
 | ||||
| //
 | ||||
| // For a `FormatSpec` to be valid at compile-time, it must be provided as
 | ||||
| // either:
 | ||||
| //
 | ||||
| // * A `constexpr` literal or `absl::string_view`, which is how it most often
 | ||||
| //   used.
 | ||||
| // * A `ParsedFormat` instantiation, which ensures the format std::string is
 | ||||
| //   valid before use. (See below.)
 | ||||
| //
 | ||||
| // Example:
 | ||||
| //
 | ||||
| //   // Provided as a std::string literal.
 | ||||
| //   absl::StrFormat("Welcome to %s, Number %d!", "The Village", 6);
 | ||||
| //
 | ||||
| //   // Provided as a constexpr absl::string_view.
 | ||||
| //   constexpr absl::string_view formatString = "Welcome to %s, Number %d!";
 | ||||
| //   absl::StrFormat(formatString, "The Village", 6);
 | ||||
| //
 | ||||
| //   // Provided as a pre-compiled ParsedFormat object.
 | ||||
| //   // Note that this example is useful only for illustration purposes.
 | ||||
| //   absl::ParsedFormat<'s', 'd'> formatString("Welcome to %s, Number %d!");
 | ||||
| //   absl::StrFormat(formatString, "TheVillage", 6);
 | ||||
| //
 | ||||
| // A format std::string generally follows the POSIX syntax as used within the POSIX
 | ||||
| // `printf` specification.
 | ||||
| //
 | ||||
| // (See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/printf.html.)
 | ||||
| //
 | ||||
| // In specific, the `FormatSpec` supports the following type specifiers:
 | ||||
| //   * `c` for characters
 | ||||
| //   * `s` for strings
 | ||||
| //   * `d` or `i` for integers
 | ||||
| //   * `o` for unsigned integer conversions into octal
 | ||||
| //   * `x` or `X` for unsigned integer conversions into hex
 | ||||
| //   * `u` for unsigned integers
 | ||||
| //   * `f` or `F` for floating point values into decimal notation
 | ||||
| //   * `e` or `E` for floating point values into exponential notation
 | ||||
| //   * `a` or `A` for floating point values into hex exponential notation
 | ||||
| //   * `g` or `G` for floating point values into decimal or exponential
 | ||||
| //     notation based on their precision
 | ||||
| //   * `p` for pointer address values
 | ||||
| //   * `n` for the special case of writing out the number of characters
 | ||||
| //     written to this point. The resulting value must be captured within an
 | ||||
| //     `absl::FormatCountCapture` type.
 | ||||
| //
 | ||||
| // NOTE: `o`, `x\X` and `u` will convert signed values to their unsigned
 | ||||
| // counterpart before formatting.
 | ||||
| //
 | ||||
| // Examples:
 | ||||
| //     "%c", 'a'                -> "a"
 | ||||
| //     "%c", 32                 -> " "
 | ||||
| //     "%s", "C"                -> "C"
 | ||||
| //     "%s", std::string("C++") -> "C++"
 | ||||
| //     "%d", -10                -> "-10"
 | ||||
| //     "%o", 10                 -> "12"
 | ||||
| //     "%x", 16                 -> "10"
 | ||||
| //     "%f", 123456789          -> "123456789.000000"
 | ||||
| //     "%e", .01                -> "1.00000e-2"
 | ||||
| //     "%a", -3.0               -> "-0x1.8p+1"
 | ||||
| //     "%g", .01                -> "1e-2"
 | ||||
| //     "%p", *int               -> "0x7ffdeb6ad2a4"
 | ||||
| //
 | ||||
| //     int n = 0;
 | ||||
| //     std::string s = absl::StrFormat(
 | ||||
| //         "%s%d%n", "hello", 123, absl::FormatCountCapture(&n));
 | ||||
| //     EXPECT_EQ(8, n);
 | ||||
| //
 | ||||
| // The `FormatSpec` intrinsically supports all of these fundamental C++ types:
 | ||||
| //
 | ||||
| // *   Characters: `char`, `signed char`, `unsigned char`
 | ||||
| // *   Integers: `int`, `short`, `unsigned short`, `unsigned`, `long`,
 | ||||
| //         `unsigned long`, `long long`, `unsigned long long`
 | ||||
| // *   Floating-point: `float`, `double`, `long double`
 | ||||
| //
 | ||||
| // However, in the `str_format` library, a format conversion specifies a broader
 | ||||
| // C++ conceptual category instead of an exact type. For example, `%s` binds to
 | ||||
| // any std::string-like argument, so `std::string`, `absl::string_view`, and
 | ||||
| // `const char*` are all accepted. Likewise, `%d` accepts any integer-like
 | ||||
| // argument, etc.
 | ||||
| 
 | ||||
| template <typename... Args> | ||||
| using FormatSpec = | ||||
|     typename str_format_internal::FormatSpecDeductionBarrier<Args...>::type; | ||||
| 
 | ||||
| using absl::str_format_internal::ExtendedParsedFormat; | ||||
| 
 | ||||
| // ParsedFormat
 | ||||
| //
 | ||||
| // A `ParsedFormat` is a class template representing a preparsed `FormatSpec`,
 | ||||
| // with template arguments specifying the conversion characters used within the
 | ||||
| // format std::string. Such characters must be valid format type specifiers, and
 | ||||
| // these type specifiers are checked at compile-time.
 | ||||
| //
 | ||||
| // Instances of `ParsedFormat` can be created, copied, and reused to speed up
 | ||||
| // formatting loops. A `ParsedFormat` may either be constructed statically, or
 | ||||
| // dynamically through its `New()` factory function, which only constructs a
 | ||||
| // runtime object if the format is valid at that time.
 | ||||
| //
 | ||||
| // Example:
 | ||||
| //
 | ||||
| //   // Verified at compile time.
 | ||||
| //   absl::ParsedFormat<'s', 'd'> formatString("Welcome to %s, Number %d!");
 | ||||
| //   absl::StrFormat(formatString, "TheVillage", 6);
 | ||||
| //
 | ||||
| //   // Verified at runtime.
 | ||||
| //   auto format_runtime = absl::ParsedFormat<'d'>::New(format_string);
 | ||||
| //   if (format_runtime) {
 | ||||
| //     value = absl::StrFormat(*format_runtime, i);
 | ||||
| //   } else {
 | ||||
| //     ... error case ...
 | ||||
| //   }
 | ||||
| template <char... Conv> | ||||
| using ParsedFormat = str_format_internal::ExtendedParsedFormat< | ||||
|     str_format_internal::ConversionCharToConv(Conv)...>; | ||||
| 
 | ||||
| // StrFormat()
 | ||||
| //
 | ||||
| // Returns a `string` given a `printf()`-style format std::string and zero or more
 | ||||
| // additional arguments. Use it as you would `sprintf()`. `StrFormat()` is the
 | ||||
| // primary formatting function within the `str_format` library, and should be
 | ||||
| // used in most cases where you need type-safe conversion of types into
 | ||||
| // formatted strings.
 | ||||
| //
 | ||||
| // The format std::string generally consists of ordinary character data along with
 | ||||
| // one or more format conversion specifiers (denoted by the `%` character).
 | ||||
| // Ordinary character data is returned unchanged into the result std::string, while
 | ||||
| // each conversion specification performs a type substitution from
 | ||||
| // `StrFormat()`'s other arguments. See the comments for `FormatSpec` for full
 | ||||
| // information on the makeup of this format std::string.
 | ||||
| //
 | ||||
| // Example:
 | ||||
| //
 | ||||
| //   std::string s = absl::StrFormat(
 | ||||
| //       "Welcome to %s, Number %d!", "The Village", 6);
 | ||||
| //   EXPECT_EQ("Welcome to The Village, Number 6!", s);
 | ||||
| //
 | ||||
| // Returns an empty std::string in case of error.
 | ||||
| template <typename... Args> | ||||
| ABSL_MUST_USE_RESULT std::string StrFormat(const FormatSpec<Args...>& format, | ||||
|                                       const Args&... args) { | ||||
|   return str_format_internal::FormatPack( | ||||
|       str_format_internal::UntypedFormatSpecImpl::Extract(format), | ||||
|       {str_format_internal::FormatArgImpl(args)...}); | ||||
| } | ||||
| 
 | ||||
| // StrAppendFormat()
 | ||||
| //
 | ||||
| // Appends to a `dst` std::string given a format std::string, and zero or more additional
 | ||||
| // arguments, returning `*dst` as a convenience for chaining purposes. Appends
 | ||||
| // nothing in case of error (but possibly alters its capacity).
 | ||||
| //
 | ||||
| // Example:
 | ||||
| //
 | ||||
| //   std::string orig("For example PI is approximately ");
 | ||||
| //   std::cout << StrAppendFormat(&orig, "%12.6f", 3.14);
 | ||||
| template <typename... Args> | ||||
| std::string& StrAppendFormat(std::string* dst, const FormatSpec<Args...>& format, | ||||
|                         const Args&... args) { | ||||
|   return str_format_internal::AppendPack( | ||||
|       dst, str_format_internal::UntypedFormatSpecImpl::Extract(format), | ||||
|       {str_format_internal::FormatArgImpl(args)...}); | ||||
| } | ||||
| 
 | ||||
| // StreamFormat()
 | ||||
| //
 | ||||
| // Writes to an output stream given a format std::string and zero or more arguments,
 | ||||
| // generally in a manner that is more efficient than streaming the result of
 | ||||
| // `absl:: StrFormat()`. The returned object must be streamed before the full
 | ||||
| // expression ends.
 | ||||
| //
 | ||||
| // Example:
 | ||||
| //
 | ||||
| //   std::cout << StreamFormat("%12.6f", 3.14);
 | ||||
| template <typename... Args> | ||||
| ABSL_MUST_USE_RESULT str_format_internal::Streamable StreamFormat( | ||||
|     const FormatSpec<Args...>& format, const Args&... args) { | ||||
|   return str_format_internal::Streamable( | ||||
|       str_format_internal::UntypedFormatSpecImpl::Extract(format), | ||||
|       {str_format_internal::FormatArgImpl(args)...}); | ||||
| } | ||||
| 
 | ||||
| // PrintF()
 | ||||
| //
 | ||||
| // Writes to stdout given a format std::string and zero or more arguments. This
 | ||||
| // function is functionally equivalent to `std::print()` (and type-safe); prefer
 | ||||
| // `absl::PrintF()` over `std::printf()`.
 | ||||
| //
 | ||||
| // Example:
 | ||||
| //
 | ||||
| //   std::string_view s = "Ulaanbaatar";
 | ||||
| //   absl::PrintF("The capital of Mongolia is: %s \n", s);
 | ||||
| //
 | ||||
| //   Outputs: "The capital of Mongolia is Ulaanbaatar"
 | ||||
| //
 | ||||
| template <typename... Args> | ||||
| int PrintF(const FormatSpec<Args...>& format, const Args&... args) { | ||||
|   return str_format_internal::FprintF( | ||||
|       stdout, str_format_internal::UntypedFormatSpecImpl::Extract(format), | ||||
|       {str_format_internal::FormatArgImpl(args)...}); | ||||
| } | ||||
| 
 | ||||
| // FPrintF()
 | ||||
| //
 | ||||
| // Writes to a file given a format std::string and zero or more arguments. This
 | ||||
| // function is functionally equivalent to `std::fprint()` (and type-safe);
 | ||||
| // prefer `absl::FPrintF()` over `std::fprintf()`.
 | ||||
| //
 | ||||
| // Example:
 | ||||
| //
 | ||||
| //   std::string_view s = "Ulaanbaatar";
 | ||||
| //   absl::FPrintF("The capital of Mongolia is: %s \n", s);
 | ||||
| //
 | ||||
| //   Outputs: "The capital of Mongolia is Ulaanbaatar"
 | ||||
| //
 | ||||
| template <typename... Args> | ||||
| int FPrintF(std::FILE* output, const FormatSpec<Args...>& format, | ||||
|             const Args&... args) { | ||||
|   return str_format_internal::FprintF( | ||||
|       output, str_format_internal::UntypedFormatSpecImpl::Extract(format), | ||||
|       {str_format_internal::FormatArgImpl(args)...}); | ||||
| } | ||||
| 
 | ||||
| // SNPrintF()
 | ||||
| //
 | ||||
| // Writes to a sized buffer given a format std::string and zero or more arguments.
 | ||||
| // This function is functionally equivalent to `std::snprint()` (and type-safe);
 | ||||
| // prefer `absl::SNPrintF()` over `std::snprintf()`.
 | ||||
| //
 | ||||
| // Example:
 | ||||
| //
 | ||||
| //   std::string_view s = "Ulaanbaatar";
 | ||||
| //   absl::FPrintF("The capital of Mongolia is: %s \n", s);
 | ||||
| //
 | ||||
| //   Outputs: "The capital of Mongolia is Ulaanbaatar"
 | ||||
| //
 | ||||
| template <typename... Args> | ||||
| int SNPrintF(char* output, std::size_t size, const FormatSpec<Args...>& format, | ||||
|              const Args&... args) { | ||||
|   return str_format_internal::SnprintF( | ||||
|       output, size, str_format_internal::UntypedFormatSpecImpl::Extract(format), | ||||
|       {str_format_internal::FormatArgImpl(args)...}); | ||||
| } | ||||
| 
 | ||||
| // -----------------------------------------------------------------------------
 | ||||
| // Custom Output Formatting Functions
 | ||||
| // -----------------------------------------------------------------------------
 | ||||
| 
 | ||||
| // FormatRawSink
 | ||||
| //
 | ||||
| // FormatRawSink is a type erased wrapper around arbitrary sink objects
 | ||||
| // specifically used as an argument to `Format()`.
 | ||||
| // FormatRawSink does not own the passed sink object. The passed object must
 | ||||
| // outlive the FormatRawSink.
 | ||||
| class FormatRawSink { | ||||
|  public: | ||||
|   // Implicitly convert from any type that provides the hook function as
 | ||||
|   // described above.
 | ||||
|   template <typename T, | ||||
|             typename = typename std::enable_if<std::is_constructible< | ||||
|                 str_format_internal::FormatRawSinkImpl, T*>::value>::type> | ||||
|   FormatRawSink(T* raw)  // NOLINT
 | ||||
|       : sink_(raw) {} | ||||
| 
 | ||||
|  private: | ||||
|   friend str_format_internal::FormatRawSinkImpl; | ||||
|   str_format_internal::FormatRawSinkImpl sink_; | ||||
| }; | ||||
| 
 | ||||
| // Format()
 | ||||
| //
 | ||||
| // Writes a formatted std::string to an arbitrary sink object (implementing the
 | ||||
| // `absl::FormatRawSink` interface), using a format std::string and zero or more
 | ||||
| // additional arguments.
 | ||||
| //
 | ||||
| // By default, `string` and `std::ostream` are supported as destination objects.
 | ||||
| //
 | ||||
| // `absl::Format()` is a generic version of `absl::StrFormat(), for custom
 | ||||
| // sinks. The format std::string, like format strings for `StrFormat()`, is checked
 | ||||
| // at compile-time.
 | ||||
| //
 | ||||
| // On failure, this function returns `false` and the state of the sink is
 | ||||
| // unspecified.
 | ||||
| template <typename... Args> | ||||
| bool Format(FormatRawSink raw_sink, const FormatSpec<Args...>& format, | ||||
|             const Args&... args) { | ||||
|   return str_format_internal::FormatUntyped( | ||||
|       str_format_internal::FormatRawSinkImpl::Extract(raw_sink), | ||||
|       str_format_internal::UntypedFormatSpecImpl::Extract(format), | ||||
|       {str_format_internal::FormatArgImpl(args)...}); | ||||
| } | ||||
| 
 | ||||
| // FormatArg
 | ||||
| //
 | ||||
| // A type-erased handle to a format argument specifically used as an argument to
 | ||||
| // `FormatUntyped()`. You may construct `FormatArg` by passing
 | ||||
| // reference-to-const of any printable type. `FormatArg` is both copyable and
 | ||||
| // assignable. The source data must outlive the `FormatArg` instance. See
 | ||||
| // example below.
 | ||||
| //
 | ||||
| using FormatArg = str_format_internal::FormatArgImpl; | ||||
| 
 | ||||
| // FormatUntyped()
 | ||||
| //
 | ||||
| // Writes a formatted std::string to an arbitrary sink object (implementing the
 | ||||
| // `absl::FormatRawSink` interface), using an `UntypedFormatSpec` and zero or
 | ||||
| // more additional arguments.
 | ||||
| //
 | ||||
| // This function acts as the most generic formatting function in the
 | ||||
| // `str_format` library. The caller provides a raw sink, an unchecked format
 | ||||
| // std::string, and (usually) a runtime specified list of arguments; no compile-time
 | ||||
| // checking of formatting is performed within this function. As a result, a
 | ||||
| // caller should check the return value to verify that no error occurred.
 | ||||
| // On failure, this function returns `false` and the state of the sink is
 | ||||
| // unspecified.
 | ||||
| //
 | ||||
| // The arguments are provided in an `absl::Span<const absl::FormatArg>`.
 | ||||
| // Each `absl::FormatArg` object binds to a single argument and keeps a
 | ||||
| // reference to it. The values used to create the `FormatArg` objects must
 | ||||
| // outlive this function call. (See `str_format_arg.h` for information on
 | ||||
| // the `FormatArg` class.)_
 | ||||
| //
 | ||||
| // Example:
 | ||||
| //
 | ||||
| //   std::optional<std::string> FormatDynamic(const std::string& in_format,
 | ||||
| //                                       const vector<std::string>& in_args) {
 | ||||
| //     std::string out;
 | ||||
| //     std::vector<absl::FormatArg> args;
 | ||||
| //     for (const auto& v : in_args) {
 | ||||
| //       // It is important that 'v' is a reference to the objects in in_args.
 | ||||
| //       // The values we pass to FormatArg must outlive the call to
 | ||||
| //       // FormatUntyped.
 | ||||
| //       args.emplace_back(v);
 | ||||
| //     }
 | ||||
| //     absl::UntypedFormatSpec format(in_format);
 | ||||
| //     if (!absl::FormatUntyped(&out, format, args)) {
 | ||||
| //       return std::nullopt;
 | ||||
| //     }
 | ||||
| //     return std::move(out);
 | ||||
| //   }
 | ||||
| //
 | ||||
| ABSL_MUST_USE_RESULT inline bool FormatUntyped( | ||||
|     FormatRawSink raw_sink, const UntypedFormatSpec& format, | ||||
|     absl::Span<const FormatArg> args) { | ||||
|   return str_format_internal::FormatUntyped( | ||||
|       str_format_internal::FormatRawSinkImpl::Extract(raw_sink), | ||||
|       str_format_internal::UntypedFormatSpecImpl::Extract(format), args); | ||||
| } | ||||
| 
 | ||||
| }  // namespace absl
 | ||||
| #endif  // ABSL_STRINGS_STR_FORMAT_H_
 | ||||
							
								
								
									
										603
									
								
								absl/strings/str_format_test.cc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										603
									
								
								absl/strings/str_format_test.cc
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,603 @@ | |||
| 
 | ||||
| #include <cstdarg> | ||||
| #include <cstdint> | ||||
| #include <cstdio> | ||||
| #include <string> | ||||
| 
 | ||||
| #include "gmock/gmock.h" | ||||
| #include "gtest/gtest.h" | ||||
| #include "absl/strings/str_format.h" | ||||
| #include "absl/strings/string_view.h" | ||||
| 
 | ||||
| namespace absl { | ||||
| namespace { | ||||
| using str_format_internal::FormatArgImpl; | ||||
| 
 | ||||
| class FormatEntryPointTest : public ::testing::Test { }; | ||||
| 
 | ||||
| TEST_F(FormatEntryPointTest, Format) { | ||||
|   std::string sink; | ||||
|   EXPECT_TRUE(Format(&sink, "A format %d", 123)); | ||||
|   EXPECT_EQ("A format 123", sink); | ||||
|   sink.clear(); | ||||
| 
 | ||||
|   ParsedFormat<'d'> pc("A format %d"); | ||||
|   EXPECT_TRUE(Format(&sink, pc, 123)); | ||||
|   EXPECT_EQ("A format 123", sink); | ||||
| } | ||||
| TEST_F(FormatEntryPointTest, UntypedFormat) { | ||||
|   constexpr const char* formats[] = { | ||||
|     "", | ||||
|     "a", | ||||
|     "%80d", | ||||
| #if !defined(_MSC_VER) && !defined(__ANDROID__) | ||||
|     // MSVC and Android don't support positional syntax.
 | ||||
|     "complicated multipart %% %1$d format %1$0999d", | ||||
| #endif  // _MSC_VER
 | ||||
|   }; | ||||
|   for (const char* fmt : formats) { | ||||
|     std::string actual; | ||||
|     int i = 123; | ||||
|     FormatArgImpl arg_123(i); | ||||
|     absl::Span<const FormatArgImpl> args(&arg_123, 1); | ||||
|     UntypedFormatSpec format(fmt); | ||||
| 
 | ||||
|     EXPECT_TRUE(FormatUntyped(&actual, format, args)); | ||||
|     char buf[4096]{}; | ||||
|     snprintf(buf, sizeof(buf), fmt, 123); | ||||
|     EXPECT_EQ( | ||||
|         str_format_internal::FormatPack( | ||||
|             str_format_internal::UntypedFormatSpecImpl::Extract(format), args), | ||||
|         buf); | ||||
|     EXPECT_EQ(actual, buf); | ||||
|   } | ||||
|   // The internal version works with a preparsed format.
 | ||||
|   ParsedFormat<'d'> pc("A format %d"); | ||||
|   int i = 345; | ||||
|   FormatArg arg(i); | ||||
|   std::string out; | ||||
|   EXPECT_TRUE(str_format_internal::FormatUntyped( | ||||
|       &out, str_format_internal::UntypedFormatSpecImpl(&pc), {&arg, 1})); | ||||
|   EXPECT_EQ("A format 345", out); | ||||
| } | ||||
| 
 | ||||
| TEST_F(FormatEntryPointTest, StringFormat) { | ||||
|   EXPECT_EQ("123", StrFormat("%d", 123)); | ||||
|   constexpr absl::string_view view("=%d=", 4); | ||||
|   EXPECT_EQ("=123=", StrFormat(view, 123)); | ||||
| } | ||||
| 
 | ||||
| TEST_F(FormatEntryPointTest, AppendFormat) { | ||||
|   std::string s; | ||||
|   std::string& r = StrAppendFormat(&s, "%d", 123); | ||||
|   EXPECT_EQ(&s, &r);  // should be same object
 | ||||
|   EXPECT_EQ("123", r); | ||||
| } | ||||
| 
 | ||||
| TEST_F(FormatEntryPointTest, AppendFormatFail) { | ||||
|   std::string s = "orig"; | ||||
| 
 | ||||
|   UntypedFormatSpec format(" more %d"); | ||||
|   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, | ||||
|                             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)); | ||||
| } | ||||
| 
 | ||||
| TEST_F(FormatEntryPointTest, Preparsed) { | ||||
|   ParsedFormat<'d'> pc("%d"); | ||||
|   EXPECT_EQ("123", StrFormat(pc, 123)); | ||||
|   // rvalue ok?
 | ||||
|   EXPECT_EQ("123", StrFormat(ParsedFormat<'d'>("%d"), 123)); | ||||
|   constexpr absl::string_view view("=%d=", 4); | ||||
|   EXPECT_EQ("=123=", StrFormat(ParsedFormat<'d'>(view), 123)); | ||||
| } | ||||
| 
 | ||||
| TEST_F(FormatEntryPointTest, FormatCountCapture) { | ||||
|   int n = 0; | ||||
|   EXPECT_EQ("", StrFormat("%n", FormatCountCapture(&n))); | ||||
|   EXPECT_EQ(0, n); | ||||
|   EXPECT_EQ("123", StrFormat("%d%n", 123, FormatCountCapture(&n))); | ||||
|   EXPECT_EQ(3, n); | ||||
| } | ||||
| 
 | ||||
| TEST_F(FormatEntryPointTest, FormatCountCaptureWrongType) { | ||||
|   // Should reject int*.
 | ||||
|   int n = 0; | ||||
|   UntypedFormatSpec format("%d%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; | ||||
|   EXPECT_EQ("    1         2", | ||||
|             StrFormat("%5d%n%10d%n", 1, FormatCountCapture(&n1), 2, | ||||
|                       FormatCountCapture(&n2))); | ||||
|   EXPECT_EQ(5, n1); | ||||
|   EXPECT_EQ(15, n2); | ||||
| } | ||||
| 
 | ||||
| TEST_F(FormatEntryPointTest, FormatCountCaptureExample) { | ||||
|   int n; | ||||
|   std::string s; | ||||
|   StrAppendFormat(&s, "%s: %n%s\n", "(1,1)", FormatCountCapture(&n), "(1,2)"); | ||||
|   StrAppendFormat(&s, "%*s%s\n", n, "", "(2,2)"); | ||||
|   EXPECT_EQ(7, n); | ||||
|   EXPECT_EQ( | ||||
|       "(1,1): (1,2)\n" | ||||
|       "       (2,2)\n", | ||||
|       s); | ||||
| } | ||||
| 
 | ||||
| TEST_F(FormatEntryPointTest, Stream) { | ||||
|   const std::string formats[] = { | ||||
|     "", | ||||
|     "a", | ||||
|     "%80d", | ||||
| #if !defined(_MSC_VER) && !defined(__ANDROID__) | ||||
|     // MSVC doesn't support positional syntax.
 | ||||
|     "complicated multipart %% %1$d format %1$080d", | ||||
| #endif  // _MSC_VER
 | ||||
|   }; | ||||
|   std::string buf(4096, '\0'); | ||||
|   for (const auto& fmt : formats) { | ||||
|     const auto parsed = ParsedFormat<'d'>::NewAllowIgnored(fmt); | ||||
|     std::ostringstream oss; | ||||
|     oss << StreamFormat(*parsed, 123); | ||||
|     int fmt_result = snprintf(&*buf.begin(), buf.size(), fmt.c_str(), 123); | ||||
|     ASSERT_TRUE(oss) << fmt; | ||||
|     ASSERT_TRUE(fmt_result >= 0 && static_cast<size_t>(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); | ||||
|   EXPECT_EQ("hello 123", oss.str()); | ||||
|   EXPECT_TRUE(oss.good()); | ||||
| } | ||||
| 
 | ||||
| TEST_F(FormatEntryPointTest, StreamFail) { | ||||
|   std::ostringstream oss; | ||||
|   UntypedFormatSpec format("hello %d"); | ||||
|   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); | ||||
|   va_list va; | ||||
|   va_start(va, fmt); | ||||
|   int r = vsnprintf(&*buf.begin(), buf.size(), fmt, va); | ||||
|   va_end(va); | ||||
|   EXPECT_GE(r, 0); | ||||
|   EXPECT_LT(r, buf.size()); | ||||
|   buf.resize(r); | ||||
|   return buf; | ||||
| } | ||||
| 
 | ||||
| TEST_F(FormatEntryPointTest, FloatPrecisionArg) { | ||||
|   // Test that positional parameters for width and precision
 | ||||
|   // are indexed to precede the value.
 | ||||
|   // Also sanity check the same formats against snprintf.
 | ||||
|   EXPECT_EQ("0.1", StrFormat("%.1f", 0.1)); | ||||
|   EXPECT_EQ("0.1", WithSnprintf("%.1f", 0.1)); | ||||
|   EXPECT_EQ("  0.1", StrFormat("%*.1f", 5, 0.1)); | ||||
|   EXPECT_EQ("  0.1", WithSnprintf("%*.1f", 5, 0.1)); | ||||
|   EXPECT_EQ("0.1", StrFormat("%.*f", 1, 0.1)); | ||||
|   EXPECT_EQ("0.1", WithSnprintf("%.*f", 1, 0.1)); | ||||
|   EXPECT_EQ("  0.1", StrFormat("%*.*f", 5, 1, 0.1)); | ||||
|   EXPECT_EQ("  0.1", WithSnprintf("%*.*f", 5, 1, 0.1)); | ||||
| } | ||||
| namespace streamed_test { | ||||
| struct X {}; | ||||
| std::ostream& operator<<(std::ostream& os, const X&) { | ||||
|   return os << "X"; | ||||
| } | ||||
| }  // streamed_test
 | ||||
| 
 | ||||
| TEST_F(FormatEntryPointTest, FormatStreamed) { | ||||
|   EXPECT_EQ("123", StrFormat("%s", FormatStreamed(123))); | ||||
|   EXPECT_EQ("  123", StrFormat("%5s", FormatStreamed(123))); | ||||
|   EXPECT_EQ("123  ", StrFormat("%-5s", FormatStreamed(123))); | ||||
|   EXPECT_EQ("X", StrFormat("%s", FormatStreamed(streamed_test::X()))); | ||||
|   EXPECT_EQ("123", StrFormat("%s", 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 { | ||||
|  public: | ||||
|   TempFile() : file_(std::tmpfile()) {} | ||||
|   ~TempFile() { std::fclose(file_); } | ||||
| 
 | ||||
|   std::FILE* file() const { return file_; } | ||||
| 
 | ||||
|   // Read the file into a std::string.
 | ||||
|   std::string ReadFile() { | ||||
|     std::fseek(file_, 0, SEEK_END); | ||||
|     int size = std::ftell(file_); | ||||
|     std::rewind(file_); | ||||
|     std::string str(2 * size, ' '); | ||||
|     int read_bytes = std::fread(&str[0], 1, str.size(), file_); | ||||
|     EXPECT_EQ(read_bytes, size); | ||||
|     str.resize(read_bytes); | ||||
|     EXPECT_TRUE(std::feof(file_)); | ||||
|     return str; | ||||
|   } | ||||
| 
 | ||||
|  private: | ||||
|   std::FILE* file_; | ||||
| }; | ||||
| 
 | ||||
| TEST_F(FormatEntryPointTest, FPrintF) { | ||||
|   TempFile tmp; | ||||
|   int result = | ||||
|       FPrintF(tmp.file(), "STRING: %s 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"); | ||||
|   EXPECT_LT(result, 0); | ||||
|   EXPECT_EQ(errno, EBADF); | ||||
| } | ||||
| 
 | ||||
| #if __GNUC__ | ||||
| TEST_F(FormatEntryPointTest, FprintfTooLarge) { | ||||
|   std::FILE* f = std::fopen("/dev/null", "w"); | ||||
|   int width = 2000000000; | ||||
|   errno = 0; | ||||
|   int result = FPrintF(f, "%*d %*d", width, 0, width, 0); | ||||
|   EXPECT_LT(result, 0); | ||||
|   EXPECT_EQ(errno, EFBIG); | ||||
|   std::fclose(f); | ||||
| } | ||||
| 
 | ||||
| TEST_F(FormatEntryPointTest, PrintF) { | ||||
|   int stdout_tmp = dup(STDOUT_FILENO); | ||||
| 
 | ||||
|   TempFile tmp; | ||||
|   std::fflush(stdout); | ||||
|   dup2(fileno(tmp.file()), STDOUT_FILENO); | ||||
| 
 | ||||
|   int result = PrintF("STRING: %s 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  // __GNUC__
 | ||||
| 
 | ||||
| TEST_F(FormatEntryPointTest, SNPrintF) { | ||||
|   char buffer[16]; | ||||
|   int result = | ||||
|       SNPrintF(buffer, sizeof(buffer), "STRING: %s", 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"); | ||||
| 
 | ||||
|   result = SNPrintF(nullptr, 0, "Just checking the %s 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); | ||||
|   // The format of a replacement is
 | ||||
|   // '%'[position][flags][width['.'precision]][length_modifier][format]
 | ||||
|   EXPECT_EQ(absl::StrFormat("%1$+3.2Lf", 1.1), "+1.10"); | ||||
|   // Text conversion:
 | ||||
|   //     "c" - Character.              Eg: 'a' -> "A", 20 -> " "
 | ||||
|   EXPECT_EQ(StrFormat("%c", 'a'), "a"); | ||||
|   EXPECT_EQ(StrFormat("%c", 0x20), " "); | ||||
|   //           Formats char and integral types: int, long, uint64_t, etc.
 | ||||
|   EXPECT_EQ(StrFormat("%c", int{'a'}), "a"); | ||||
|   EXPECT_EQ(StrFormat("%c", long{'a'}), "a");  // NOLINT
 | ||||
|   EXPECT_EQ(StrFormat("%c", uint64_t{'a'}), "a"); | ||||
|   //     "s" - std::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("%s", std::string("C++")), "C++"); | ||||
|   EXPECT_EQ(StrFormat("%s", string_view("view")), "view"); | ||||
|   // Integral Conversion
 | ||||
|   //     These format integral types: char, int, long, uint64_t, etc.
 | ||||
|   EXPECT_EQ(StrFormat("%d", char{10}), "10"); | ||||
|   EXPECT_EQ(StrFormat("%d", int{10}), "10"); | ||||
|   EXPECT_EQ(StrFormat("%d", long{10}), "10");  // NOLINT
 | ||||
|   EXPECT_EQ(StrFormat("%d", uint64_t{10}), "10"); | ||||
|   //     d,i - signed decimal          Eg: -10 -> "-10"
 | ||||
|   EXPECT_EQ(StrFormat("%d", -10), "-10"); | ||||
|   EXPECT_EQ(StrFormat("%i", -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"); | ||||
|   //     x/X - lower,upper case hex    Eg:  10 -> "a"/"A"
 | ||||
|   EXPECT_EQ(StrFormat("%x", 10), "a"); | ||||
|   EXPECT_EQ(StrFormat("%X", 10), "A"); | ||||
|   // Floating-point, with upper/lower-case output.
 | ||||
|   //     These format floating points types: float, double, long double, etc.
 | ||||
|   EXPECT_EQ(StrFormat("%.1f", float{1}), "1.0"); | ||||
|   EXPECT_EQ(StrFormat("%.1f", double{1}), "1.0"); | ||||
|   const long double long_double = 1.0; | ||||
|   EXPECT_EQ(StrFormat("%.1f", long_double), "1.0"); | ||||
|   //     These also format integral types: char, int, long, uint64_t, etc.:
 | ||||
|   EXPECT_EQ(StrFormat("%.1f", char{1}), "1.0"); | ||||
|   EXPECT_EQ(StrFormat("%.1f", int{1}), "1.0"); | ||||
|   EXPECT_EQ(StrFormat("%.1f", long{1}), "1.0");  // NOLINT
 | ||||
|   EXPECT_EQ(StrFormat("%.1f", uint64_t{1}), "1.0"); | ||||
|   //     f/F - decimal.                Eg: 123456789 -> "123456789.000000"
 | ||||
|   EXPECT_EQ(StrFormat("%f", 123456789), "123456789.000000"); | ||||
|   EXPECT_EQ(StrFormat("%F", 123456789), "123456789.000000"); | ||||
|   //     e/E - exponentiated           Eg: .01 -> "1.00000e-2"/"1.00000E-2"
 | ||||
|   EXPECT_EQ(StrFormat("%e", .01), "1.000000e-02"); | ||||
|   EXPECT_EQ(StrFormat("%E", .01), "1.000000E-02"); | ||||
|   //     g/G - exponentiate to fit     Eg: .01 -> "0.01", 1e10 ->"1e+10"/"1E+10"
 | ||||
|   EXPECT_EQ(StrFormat("%g", .01), "0.01"); | ||||
|   EXPECT_EQ(StrFormat("%g", 1e10), "1e+10"); | ||||
|   EXPECT_EQ(StrFormat("%G", 1e10), "1E+10"); | ||||
|   //     a/A - lower,upper case hex    Eg: -3.0 -> "-0x1.8p+1"/"-0X1.8P+1"
 | ||||
| 
 | ||||
| // On NDK r16, there is a regression in hexfloat formatting.
 | ||||
| #if !defined(__NDK_MAJOR__) || __NDK_MAJOR__ != 16 | ||||
|   EXPECT_EQ(StrFormat("%.1a", -3.0), "-0x1.8p+1");  // .1 to fix MSVC output
 | ||||
|   EXPECT_EQ(StrFormat("%.1A", -3.0), "-0X1.8P+1");  // .1 to fix MSVC output
 | ||||
| #endif | ||||
| 
 | ||||
|   // Other conversion
 | ||||
|   int64_t value = 0x7ffdeb6; | ||||
|   auto ptr_value = static_cast<uintptr_t>(value); | ||||
|   const int& something = *reinterpret_cast<const int*>(ptr_value); | ||||
|   EXPECT_EQ(StrFormat("%p", &something), StrFormat("0x%x", ptr_value)); | ||||
| 
 | ||||
|   // Output widths are supported, with optional flags.
 | ||||
|   EXPECT_EQ(StrFormat("%3d", 1), "  1"); | ||||
|   EXPECT_EQ(StrFormat("%3d", 123456), "123456"); | ||||
|   EXPECT_EQ(StrFormat("%06.2f", 1.234), "001.23"); | ||||
|   EXPECT_EQ(StrFormat("%+d", 1), "+1"); | ||||
|   EXPECT_EQ(StrFormat("% d", 1), " 1"); | ||||
|   EXPECT_EQ(StrFormat("%-4d", -1), "-1  "); | ||||
|   EXPECT_EQ(StrFormat("%#o", 10), "012"); | ||||
|   EXPECT_EQ(StrFormat("%#x", 15), "0xf"); | ||||
|   EXPECT_EQ(StrFormat("%04d", 8), "0008"); | ||||
|   // Posix positional substitution.
 | ||||
|   EXPECT_EQ(absl::StrFormat("%2$s, %3$s, %1$s!", "vici", "veni", "vidi"), | ||||
|             "veni, vidi, vici!"); | ||||
|   // Length modifiers are ignored.
 | ||||
|   EXPECT_EQ(StrFormat("%hhd", int{1}), "1"); | ||||
|   EXPECT_EQ(StrFormat("%hd", int{1}), "1"); | ||||
|   EXPECT_EQ(StrFormat("%ld", int{1}), "1"); | ||||
|   EXPECT_EQ(StrFormat("%lld", int{1}), "1"); | ||||
|   EXPECT_EQ(StrFormat("%Ld", int{1}), "1"); | ||||
|   EXPECT_EQ(StrFormat("%jd", int{1}), "1"); | ||||
|   EXPECT_EQ(StrFormat("%zd", int{1}), "1"); | ||||
|   EXPECT_EQ(StrFormat("%td", int{1}), "1"); | ||||
|   EXPECT_EQ(StrFormat("%qd", int{1}), "1"); | ||||
| } | ||||
| 
 | ||||
| using str_format_internal::ExtendedParsedFormat; | ||||
| using str_format_internal::ParsedFormatBase; | ||||
| 
 | ||||
| struct SummarizeConsumer { | ||||
|   std::string* out; | ||||
|   explicit SummarizeConsumer(std::string* out) : out(out) {} | ||||
| 
 | ||||
|   bool Append(string_view s) { | ||||
|     *out += "[" + std::string(s) + "]"; | ||||
|     return true; | ||||
|   } | ||||
| 
 | ||||
|   bool ConvertOne(const str_format_internal::UnboundConversion& conv, | ||||
|                   string_view s) { | ||||
|     *out += "{"; | ||||
|     *out += std::string(s); | ||||
|     *out += ":"; | ||||
|     *out += std::to_string(conv.arg_position) + "$"; | ||||
|     if (conv.width.is_from_arg()) { | ||||
|       *out += std::to_string(conv.width.get_from_arg()) + "$*"; | ||||
|     } | ||||
|     if (conv.precision.is_from_arg()) { | ||||
|       *out += "." + std::to_string(conv.precision.get_from_arg()) + "$*"; | ||||
|     } | ||||
|     *out += conv.conv.Char(); | ||||
|     *out += "}"; | ||||
|     return true; | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| std::string SummarizeParsedFormat(const ParsedFormatBase& pc) { | ||||
|   std::string out; | ||||
|   if (!pc.ProcessFormat(SummarizeConsumer(&out))) out += "!"; | ||||
|   return out; | ||||
| } | ||||
| 
 | ||||
| class ParsedFormatTest : public testing::Test {}; | ||||
| 
 | ||||
| TEST_F(ParsedFormatTest, SimpleChecked) { | ||||
|   EXPECT_EQ("[ABC]{d:1$d}[DEF]", | ||||
|             SummarizeParsedFormat(ParsedFormat<'d'>("ABC%dDEF"))); | ||||
|   EXPECT_EQ("{s:1$s}[FFF]{d:2$d}[ZZZ]{f:3$f}", | ||||
|             SummarizeParsedFormat(ParsedFormat<'s', 'd', 'f'>("%sFFF%dZZZ%f"))); | ||||
|   EXPECT_EQ("{s:1$s}[ ]{.*d:3$.2$*d}", | ||||
|             SummarizeParsedFormat(ParsedFormat<'s', '*', 'd'>("%s %.*d"))); | ||||
| } | ||||
| 
 | ||||
| TEST_F(ParsedFormatTest, SimpleUncheckedCorrect) { | ||||
|   auto f = ParsedFormat<'d'>::New("ABC%dDEF"); | ||||
|   ASSERT_TRUE(f); | ||||
|   EXPECT_EQ("[ABC]{d:1$d}[DEF]", SummarizeParsedFormat(*f)); | ||||
| 
 | ||||
|   std::string format = "%sFFF%dZZZ%f"; | ||||
|   auto f2 = ParsedFormat<'s', 'd', 'f'>::New(format); | ||||
| 
 | ||||
|   ASSERT_TRUE(f2); | ||||
|   EXPECT_EQ("{s:1$s}[FFF]{d:2$d}[ZZZ]{f:3$f}", SummarizeParsedFormat(*f2)); | ||||
| 
 | ||||
|   f2 = ParsedFormat<'s', 'd', 'f'>::New("%s %d %f"); | ||||
| 
 | ||||
|   ASSERT_TRUE(f2); | ||||
|   EXPECT_EQ("{s:1$s}[ ]{d:2$d}[ ]{f:3$f}", SummarizeParsedFormat(*f2)); | ||||
| 
 | ||||
|   auto star = ParsedFormat<'*', 'd'>::New("%*d"); | ||||
|   ASSERT_TRUE(star); | ||||
|   EXPECT_EQ("{*d:2$1$*d}", SummarizeParsedFormat(*star)); | ||||
| 
 | ||||
|   auto dollar = ParsedFormat<'d', 's'>::New("%2$s %1$d"); | ||||
|   ASSERT_TRUE(dollar); | ||||
|   EXPECT_EQ("{2$s:2$s}[ ]{1$d:1$d}", SummarizeParsedFormat(*dollar)); | ||||
|   // with reuse
 | ||||
|   dollar = ParsedFormat<'d', 's'>::New("%2$s %1$d %1$d"); | ||||
|   ASSERT_TRUE(dollar); | ||||
|   EXPECT_EQ("{2$s:2$s}[ ]{1$d:1$d}[ ]{1$d:1$d}", | ||||
|             SummarizeParsedFormat(*dollar)); | ||||
| } | ||||
| 
 | ||||
| TEST_F(ParsedFormatTest, SimpleUncheckedIgnoredArgs) { | ||||
|   EXPECT_FALSE((ParsedFormat<'d', 's'>::New("ABC"))); | ||||
|   EXPECT_FALSE((ParsedFormat<'d', 's'>::New("%dABC"))); | ||||
|   EXPECT_FALSE((ParsedFormat<'d', 's'>::New("ABC%2$s"))); | ||||
|   auto f = ParsedFormat<'d', 's'>::NewAllowIgnored("ABC"); | ||||
|   ASSERT_TRUE(f); | ||||
|   EXPECT_EQ("[ABC]", SummarizeParsedFormat(*f)); | ||||
|   f = ParsedFormat<'d', 's'>::NewAllowIgnored("%dABC"); | ||||
|   ASSERT_TRUE(f); | ||||
|   EXPECT_EQ("{d:1$d}[ABC]", SummarizeParsedFormat(*f)); | ||||
|   f = ParsedFormat<'d', 's'>::NewAllowIgnored("ABC%2$s"); | ||||
|   ASSERT_TRUE(f); | ||||
|   EXPECT_EQ("[ABC]{2$s:2$s}", SummarizeParsedFormat(*f)); | ||||
| } | ||||
| 
 | ||||
| TEST_F(ParsedFormatTest, SimpleUncheckedUnsupported) { | ||||
|   EXPECT_FALSE(ParsedFormat<'d'>::New("%1$d %1$x")); | ||||
|   EXPECT_FALSE(ParsedFormat<'x'>::New("%1$d %1$x")); | ||||
| } | ||||
| 
 | ||||
| TEST_F(ParsedFormatTest, SimpleUncheckedIncorrect) { | ||||
|   EXPECT_FALSE(ParsedFormat<'d'>::New("")); | ||||
| 
 | ||||
|   EXPECT_FALSE(ParsedFormat<'d'>::New("ABC%dDEF%d")); | ||||
| 
 | ||||
|   std::string format = "%sFFF%dZZZ%f"; | ||||
|   EXPECT_FALSE((ParsedFormat<'s', 'd', 'g'>::New(format))); | ||||
| } | ||||
| 
 | ||||
| using str_format_internal::Conv; | ||||
| 
 | ||||
| TEST_F(ParsedFormatTest, UncheckedCorrect) { | ||||
|   auto f = ExtendedParsedFormat<Conv::d>::New("ABC%dDEF"); | ||||
|   ASSERT_TRUE(f); | ||||
|   EXPECT_EQ("[ABC]{d:1$d}[DEF]", SummarizeParsedFormat(*f)); | ||||
| 
 | ||||
|   std::string format = "%sFFF%dZZZ%f"; | ||||
|   auto f2 = | ||||
|       ExtendedParsedFormat<Conv::string, Conv::d, Conv::floating>::New(format); | ||||
| 
 | ||||
|   ASSERT_TRUE(f2); | ||||
|   EXPECT_EQ("{s:1$s}[FFF]{d:2$d}[ZZZ]{f:3$f}", SummarizeParsedFormat(*f2)); | ||||
| 
 | ||||
|   f2 = ExtendedParsedFormat<Conv::string, Conv::d, Conv::floating>::New( | ||||
|       "%s %d %f"); | ||||
| 
 | ||||
|   ASSERT_TRUE(f2); | ||||
|   EXPECT_EQ("{s:1$s}[ ]{d:2$d}[ ]{f:3$f}", SummarizeParsedFormat(*f2)); | ||||
| 
 | ||||
|   auto star = ExtendedParsedFormat<Conv::star, Conv::d>::New("%*d"); | ||||
|   ASSERT_TRUE(star); | ||||
|   EXPECT_EQ("{*d:2$1$*d}", SummarizeParsedFormat(*star)); | ||||
| 
 | ||||
|   auto dollar = ExtendedParsedFormat<Conv::d, Conv::s>::New("%2$s %1$d"); | ||||
|   ASSERT_TRUE(dollar); | ||||
|   EXPECT_EQ("{2$s:2$s}[ ]{1$d:1$d}", SummarizeParsedFormat(*dollar)); | ||||
|   // with reuse
 | ||||
|   dollar = ExtendedParsedFormat<Conv::d, Conv::s>::New("%2$s %1$d %1$d"); | ||||
|   ASSERT_TRUE(dollar); | ||||
|   EXPECT_EQ("{2$s:2$s}[ ]{1$d:1$d}[ ]{1$d:1$d}", | ||||
|             SummarizeParsedFormat(*dollar)); | ||||
| } | ||||
| 
 | ||||
| TEST_F(ParsedFormatTest, UncheckedIgnoredArgs) { | ||||
|   EXPECT_FALSE((ExtendedParsedFormat<Conv::d, Conv::s>::New("ABC"))); | ||||
|   EXPECT_FALSE((ExtendedParsedFormat<Conv::d, Conv::s>::New("%dABC"))); | ||||
|   EXPECT_FALSE((ExtendedParsedFormat<Conv::d, Conv::s>::New("ABC%2$s"))); | ||||
|   auto f = ExtendedParsedFormat<Conv::d, Conv::s>::NewAllowIgnored("ABC"); | ||||
|   ASSERT_TRUE(f); | ||||
|   EXPECT_EQ("[ABC]", SummarizeParsedFormat(*f)); | ||||
|   f = ExtendedParsedFormat<Conv::d, Conv::s>::NewAllowIgnored("%dABC"); | ||||
|   ASSERT_TRUE(f); | ||||
|   EXPECT_EQ("{d:1$d}[ABC]", SummarizeParsedFormat(*f)); | ||||
|   f = ExtendedParsedFormat<Conv::d, Conv::s>::NewAllowIgnored("ABC%2$s"); | ||||
|   ASSERT_TRUE(f); | ||||
|   EXPECT_EQ("[ABC]{2$s:2$s}", SummarizeParsedFormat(*f)); | ||||
| } | ||||
| 
 | ||||
| TEST_F(ParsedFormatTest, UncheckedMultipleTypes) { | ||||
|   auto dx = ExtendedParsedFormat<Conv::d | Conv::x>::New("%1$d %1$x"); | ||||
|   EXPECT_TRUE(dx); | ||||
|   EXPECT_EQ("{1$d:1$d}[ ]{1$x:1$x}", SummarizeParsedFormat(*dx)); | ||||
| 
 | ||||
|   dx = ExtendedParsedFormat<Conv::d | Conv::x>::New("%1$d"); | ||||
|   EXPECT_TRUE(dx); | ||||
|   EXPECT_EQ("{1$d:1$d}", SummarizeParsedFormat(*dx)); | ||||
| } | ||||
| 
 | ||||
| TEST_F(ParsedFormatTest, UncheckedIncorrect) { | ||||
|   EXPECT_FALSE(ExtendedParsedFormat<Conv::d>::New("")); | ||||
| 
 | ||||
|   EXPECT_FALSE(ExtendedParsedFormat<Conv::d>::New("ABC%dDEF%d")); | ||||
| 
 | ||||
|   std::string format = "%sFFF%dZZZ%f"; | ||||
|   EXPECT_FALSE((ExtendedParsedFormat<Conv::s, Conv::d, Conv::g>::New(format))); | ||||
| } | ||||
| 
 | ||||
| TEST_F(ParsedFormatTest, RegressionMixPositional) { | ||||
|   EXPECT_FALSE((ExtendedParsedFormat<Conv::d, Conv::o>::New("%1$d %o"))); | ||||
| } | ||||
| 
 | ||||
| }  // namespace
 | ||||
| }  // namespace absl
 | ||||
|  | @ -34,15 +34,13 @@ namespace { | |||
| const char kInfiniteFutureStr[] = "infinite-future"; | ||||
| const char kInfinitePastStr[] = "infinite-past"; | ||||
| 
 | ||||
| using cctz_sec = cctz::time_point<cctz::sys_seconds>; | ||||
| using cctz_fem = cctz::detail::femtoseconds; | ||||
| struct cctz_parts { | ||||
|   cctz_sec sec; | ||||
|   cctz_fem fem; | ||||
|   cctz::time_point<cctz::seconds> sec; | ||||
|   cctz::detail::femtoseconds fem; | ||||
| }; | ||||
| 
 | ||||
| inline cctz_sec unix_epoch() { | ||||
|   return std::chrono::time_point_cast<cctz::sys_seconds>( | ||||
| inline cctz::time_point<cctz::seconds> unix_epoch() { | ||||
|   return std::chrono::time_point_cast<cctz::seconds>( | ||||
|       std::chrono::system_clock::from_time_t(0)); | ||||
| } | ||||
| 
 | ||||
|  | @ -53,8 +51,8 @@ cctz_parts Split(absl::Time t) { | |||
|   const auto d = time_internal::ToUnixDuration(t); | ||||
|   const int64_t rep_hi = time_internal::GetRepHi(d); | ||||
|   const int64_t rep_lo = time_internal::GetRepLo(d); | ||||
|   const auto sec = unix_epoch() + cctz::sys_seconds(rep_hi); | ||||
|   const auto fem = cctz_fem(rep_lo * (1000 * 1000 / 4)); | ||||
|   const auto sec = unix_epoch() + cctz::seconds(rep_hi); | ||||
|   const auto fem = cctz::detail::femtoseconds(rep_lo * (1000 * 1000 / 4)); | ||||
|   return {sec, fem}; | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -34,23 +34,24 @@ namespace cctz { | |||
| // Convenience aliases. Not intended as public API points.
 | ||||
| template <typename D> | ||||
| using time_point = std::chrono::time_point<std::chrono::system_clock, D>; | ||||
| using sys_seconds = std::chrono::duration<std::int_fast64_t>; | ||||
| using seconds = std::chrono::duration<std::int_fast64_t>; | ||||
| using sys_seconds = seconds;  // Deprecated.  Use cctz::seconds instead.
 | ||||
| 
 | ||||
| namespace detail { | ||||
| template <typename D> | ||||
| inline std::pair<time_point<sys_seconds>, D> | ||||
| inline std::pair<time_point<seconds>, D> | ||||
| split_seconds(const time_point<D>& tp) { | ||||
|   auto sec = std::chrono::time_point_cast<sys_seconds>(tp); | ||||
|   auto sec = std::chrono::time_point_cast<seconds>(tp); | ||||
|   auto sub = tp - sec; | ||||
|   if (sub.count() < 0) { | ||||
|     sec -= sys_seconds(1); | ||||
|     sub += sys_seconds(1); | ||||
|     sec -= seconds(1); | ||||
|     sub += seconds(1); | ||||
|   } | ||||
|   return {sec, std::chrono::duration_cast<D>(sub)}; | ||||
| } | ||||
| inline std::pair<time_point<sys_seconds>, sys_seconds> | ||||
| split_seconds(const time_point<sys_seconds>& tp) { | ||||
|   return {tp, sys_seconds(0)}; | ||||
| inline std::pair<time_point<seconds>, seconds> | ||||
| split_seconds(const time_point<seconds>& tp) { | ||||
|   return {tp, seconds::zero()}; | ||||
| } | ||||
| }  // namespace detail
 | ||||
| 
 | ||||
|  | @ -99,7 +100,7 @@ class time_zone { | |||
|     bool is_dst;       // is offset non-standard?
 | ||||
|     const char* abbr;  // time-zone abbreviation (e.g., "PST")
 | ||||
|   }; | ||||
|   absolute_lookup lookup(const time_point<sys_seconds>& tp) const; | ||||
|   absolute_lookup lookup(const time_point<seconds>& tp) const; | ||||
|   template <typename D> | ||||
|   absolute_lookup lookup(const time_point<D>& tp) const { | ||||
|     return lookup(detail::split_seconds(tp).first); | ||||
|  | @ -120,7 +121,7 @@ class time_zone { | |||
|   // offset, the transition point itself, and the post-transition offset,
 | ||||
|   // respectively (all three times are equal if kind == UNIQUE).  If any
 | ||||
|   // of these three absolute times is outside the representable range of a
 | ||||
|   // time_point<sys_seconds> the field is set to its maximum/minimum value.
 | ||||
|   // time_point<seconds> the field is set to its maximum/minimum value.
 | ||||
|   //
 | ||||
|   // Example:
 | ||||
|   //   cctz::time_zone lax;
 | ||||
|  | @ -152,9 +153,9 @@ class time_zone { | |||
|       SKIPPED,   // the civil time did not exist (pre >= trans > post)
 | ||||
|       REPEATED,  // the civil time was ambiguous (pre < trans <= post)
 | ||||
|     } kind; | ||||
|     time_point<sys_seconds> pre;    // uses the pre-transition offset
 | ||||
|     time_point<sys_seconds> trans;  // instant of civil-offset change
 | ||||
|     time_point<sys_seconds> post;   // uses the post-transition offset
 | ||||
|     time_point<seconds> pre;    // uses the pre-transition offset
 | ||||
|     time_point<seconds> trans;  // instant of civil-offset change
 | ||||
|     time_point<seconds> post;   // uses the post-transition offset
 | ||||
|   }; | ||||
|   civil_lookup lookup(const civil_second& cs) const; | ||||
| 
 | ||||
|  | @ -180,7 +181,7 @@ time_zone utc_time_zone(); | |||
| // Returns a time zone that is a fixed offset (seconds east) from UTC.
 | ||||
| // Note: If the absolute value of the offset is greater than 24 hours
 | ||||
| // you'll get UTC (i.e., zero offset) instead.
 | ||||
| time_zone fixed_time_zone(const sys_seconds& offset); | ||||
| time_zone fixed_time_zone(const seconds& offset); | ||||
| 
 | ||||
| // Returns a time zone representing the local time zone. Falls back to UTC.
 | ||||
| time_zone local_time_zone(); | ||||
|  | @ -199,8 +200,8 @@ inline civil_second convert(const time_point<D>& tp, const time_zone& tz) { | |||
| // it was either repeated or non-existent), then the returned time_point is
 | ||||
| // the best estimate that preserves relative order. That is, this function
 | ||||
| // guarantees that if cs1 < cs2, then convert(cs1, tz) <= convert(cs2, tz).
 | ||||
| inline time_point<sys_seconds> convert(const civil_second& cs, | ||||
|                                        const time_zone& tz) { | ||||
| inline time_point<seconds> convert(const civil_second& cs, | ||||
|                                    const time_zone& tz) { | ||||
|   const time_zone::civil_lookup cl = tz.lookup(cs); | ||||
|   if (cl.kind == time_zone::civil_lookup::SKIPPED) return cl.trans; | ||||
|   return cl.pre; | ||||
|  | @ -208,10 +209,10 @@ inline time_point<sys_seconds> convert(const civil_second& cs, | |||
| 
 | ||||
| namespace detail { | ||||
| using femtoseconds = std::chrono::duration<std::int_fast64_t, std::femto>; | ||||
| std::string format(const std::string&, const time_point<sys_seconds>&, | ||||
| std::string format(const std::string&, const time_point<seconds>&, | ||||
|                    const femtoseconds&, const time_zone&); | ||||
| bool parse(const std::string&, const std::string&, const time_zone&, | ||||
|            time_point<sys_seconds>*, femtoseconds*, std::string* err = nullptr); | ||||
|            time_point<seconds>*, femtoseconds*, std::string* err = nullptr); | ||||
| }  // namespace detail
 | ||||
| 
 | ||||
| // Formats the given time_point in the given cctz::time_zone according to
 | ||||
|  | @ -298,7 +299,7 @@ inline std::string format(const std::string& fmt, const time_point<D>& tp, | |||
| template <typename D> | ||||
| inline bool parse(const std::string& fmt, const std::string& input, | ||||
|                   const time_zone& tz, time_point<D>* tpp) { | ||||
|   time_point<sys_seconds> sec; | ||||
|   time_point<seconds> sec; | ||||
|   detail::femtoseconds fs; | ||||
|   const bool b = detail::parse(fmt, input, tz, &sec, &fs); | ||||
|   if (b) { | ||||
|  |  | |||
|  | @ -42,9 +42,9 @@ int Parse02d(const char* p) { | |||
| 
 | ||||
| }  // namespace
 | ||||
| 
 | ||||
| bool FixedOffsetFromName(const std::string& name, sys_seconds* offset) { | ||||
| bool FixedOffsetFromName(const std::string& name, seconds* offset) { | ||||
|   if (name.compare(0, std::string::npos, "UTC", 3) == 0) { | ||||
|     *offset = sys_seconds::zero(); | ||||
|     *offset = seconds::zero(); | ||||
|     return true; | ||||
|   } | ||||
| 
 | ||||
|  | @ -69,12 +69,12 @@ bool FixedOffsetFromName(const std::string& name, sys_seconds* offset) { | |||
| 
 | ||||
|   secs += ((hours * 60) + mins) * 60; | ||||
|   if (secs > 24 * 60 * 60) return false;  // outside supported offset range
 | ||||
|   *offset = sys_seconds(secs * (np[0] == '-' ? -1 : 1));  // "-" means west
 | ||||
|   *offset = seconds(secs * (np[0] == '-' ? -1 : 1));  // "-" means west
 | ||||
|   return true; | ||||
| } | ||||
| 
 | ||||
| std::string FixedOffsetToName(const sys_seconds& offset) { | ||||
|   if (offset == sys_seconds::zero()) return "UTC"; | ||||
| std::string FixedOffsetToName(const seconds& offset) { | ||||
|   if (offset == seconds::zero()) return "UTC"; | ||||
|   if (offset < std::chrono::hours(-24) || offset > std::chrono::hours(24)) { | ||||
|     // We don't support fixed-offset zones more than 24 hours
 | ||||
|     // away from UTC to avoid complications in rendering such
 | ||||
|  | @ -101,7 +101,7 @@ std::string FixedOffsetToName(const sys_seconds& offset) { | |||
|   return buf; | ||||
| } | ||||
| 
 | ||||
| std::string FixedOffsetToAbbr(const sys_seconds& offset) { | ||||
| std::string FixedOffsetToAbbr(const seconds& offset) { | ||||
|   std::string abbr = FixedOffsetToName(offset); | ||||
|   const std::size_t prefix_len = sizeof(kFixedOffsetPrefix) - 1; | ||||
|   if (abbr.size() == prefix_len + 9) {         // <prefix>+99:99:99
 | ||||
|  |  | |||
|  | @ -38,9 +38,9 @@ namespace cctz { | |||
| // Note: FixedOffsetFromName() fails on syntax errors or when the parsed
 | ||||
| // offset exceeds 24 hours.  FixedOffsetToName() and FixedOffsetToAbbr()
 | ||||
| // both produce "UTC" when the argument offset exceeds 24 hours.
 | ||||
| bool FixedOffsetFromName(const std::string& name, sys_seconds* offset); | ||||
| std::string FixedOffsetToName(const sys_seconds& offset); | ||||
| std::string FixedOffsetToAbbr(const sys_seconds& offset); | ||||
| bool FixedOffsetFromName(const std::string& name, seconds* offset); | ||||
| std::string FixedOffsetToName(const seconds& offset); | ||||
| std::string FixedOffsetToAbbr(const seconds& offset); | ||||
| 
 | ||||
| }  // namespace cctz
 | ||||
| }  // namespace time_internal
 | ||||
|  |  | |||
|  | @ -277,7 +277,7 @@ const std::int_fast64_t kExp10[kDigits10_64 + 1] = { | |||
| // not support the tm_gmtoff and tm_zone extensions to std::tm.
 | ||||
| //
 | ||||
| // Requires that zero() <= fs < seconds(1).
 | ||||
| std::string format(const std::string& format, const time_point<sys_seconds>& tp, | ||||
| std::string format(const std::string& format, const time_point<seconds>& tp, | ||||
|                    const detail::femtoseconds& fs, const time_zone& tz) { | ||||
|   std::string result; | ||||
|   result.reserve(format.size());  // A reasonable guess for the result size.
 | ||||
|  | @ -555,7 +555,7 @@ const char* ParseTM(const char* dp, const char* fmt, std::tm* tm) { | |||
| // We also handle the %z specifier to accommodate platforms that do not
 | ||||
| // support the tm_gmtoff extension to std::tm.  %Z is parsed but ignored.
 | ||||
| bool parse(const std::string& format, const std::string& input, | ||||
|            const time_zone& tz, time_point<sys_seconds>* sec, | ||||
|            const time_zone& tz, time_point<seconds>* sec, | ||||
|            detail::femtoseconds* fs, std::string* err) { | ||||
|   // The unparsed input.
 | ||||
|   const char* data = input.c_str();  // NUL terminated
 | ||||
|  | @ -822,15 +822,15 @@ bool parse(const std::string& format, const std::string& input, | |||
| 
 | ||||
|   const auto tp = ptz.lookup(cs).pre; | ||||
|   // Checks for overflow/underflow and returns an error as necessary.
 | ||||
|   if (tp == time_point<sys_seconds>::max()) { | ||||
|     const auto al = ptz.lookup(time_point<sys_seconds>::max()); | ||||
|   if (tp == time_point<seconds>::max()) { | ||||
|     const auto al = ptz.lookup(time_point<seconds>::max()); | ||||
|     if (cs > al.cs) { | ||||
|       if (err != nullptr) *err = "Out-of-range field"; | ||||
|       return false; | ||||
|     } | ||||
|   } | ||||
|   if (tp == time_point<sys_seconds>::min()) { | ||||
|     const auto al = ptz.lookup(time_point<sys_seconds>::min()); | ||||
|   if (tp == time_point<seconds>::min()) { | ||||
|     const auto al = ptz.lookup(time_point<seconds>::min()); | ||||
|     if (cs < al.cs) { | ||||
|       if (err != nullptr) *err = "Out-of-range field"; | ||||
|       return false; | ||||
|  |  | |||
|  | @ -23,15 +23,7 @@ | |||
| #include "gmock/gmock.h" | ||||
| #include "gtest/gtest.h" | ||||
| 
 | ||||
| using std::chrono::time_point_cast; | ||||
| using std::chrono::system_clock; | ||||
| using std::chrono::nanoseconds; | ||||
| using std::chrono::microseconds; | ||||
| using std::chrono::milliseconds; | ||||
| using std::chrono::seconds; | ||||
| using std::chrono::minutes; | ||||
| using std::chrono::hours; | ||||
| using testing::HasSubstr; | ||||
| namespace chrono = std::chrono; | ||||
| 
 | ||||
| namespace absl { | ||||
| namespace time_internal { | ||||
|  | @ -81,33 +73,36 @@ void TestFormatSpecifier(time_point<D> tp, time_zone tz, const std::string& fmt, | |||
| TEST(Format, TimePointResolution) { | ||||
|   const char kFmt[] = "%H:%M:%E*S"; | ||||
|   const time_zone utc = utc_time_zone(); | ||||
|   const time_point<nanoseconds> t0 = system_clock::from_time_t(1420167845) + | ||||
|                                      milliseconds(123) + microseconds(456) + | ||||
|                                      nanoseconds(789); | ||||
|   EXPECT_EQ("03:04:05.123456789", | ||||
|             format(kFmt, time_point_cast<nanoseconds>(t0), utc)); | ||||
|   EXPECT_EQ("03:04:05.123456", | ||||
|             format(kFmt, time_point_cast<microseconds>(t0), utc)); | ||||
|   EXPECT_EQ("03:04:05.123", | ||||
|             format(kFmt, time_point_cast<milliseconds>(t0), utc)); | ||||
|   const time_point<chrono::nanoseconds> t0 = | ||||
|       chrono::system_clock::from_time_t(1420167845) + | ||||
|       chrono::milliseconds(123) + chrono::microseconds(456) + | ||||
|       chrono::nanoseconds(789); | ||||
|   EXPECT_EQ( | ||||
|       "03:04:05.123456789", | ||||
|       format(kFmt, chrono::time_point_cast<chrono::nanoseconds>(t0), utc)); | ||||
|   EXPECT_EQ( | ||||
|       "03:04:05.123456", | ||||
|       format(kFmt, chrono::time_point_cast<chrono::microseconds>(t0), utc)); | ||||
|   EXPECT_EQ( | ||||
|       "03:04:05.123", | ||||
|       format(kFmt, chrono::time_point_cast<chrono::milliseconds>(t0), utc)); | ||||
|   EXPECT_EQ("03:04:05", | ||||
|             format(kFmt, time_point_cast<seconds>(t0), utc)); | ||||
|             format(kFmt, chrono::time_point_cast<chrono::seconds>(t0), utc)); | ||||
|   EXPECT_EQ("03:04:05", | ||||
|             format(kFmt, time_point_cast<sys_seconds>(t0), utc)); | ||||
|             format(kFmt, chrono::time_point_cast<absl::time_internal::cctz::seconds>(t0), utc)); | ||||
|   EXPECT_EQ("03:04:00", | ||||
|             format(kFmt, time_point_cast<minutes>(t0), utc)); | ||||
|             format(kFmt, chrono::time_point_cast<chrono::minutes>(t0), utc)); | ||||
|   EXPECT_EQ("03:00:00", | ||||
|             format(kFmt, time_point_cast<hours>(t0), utc)); | ||||
|             format(kFmt, chrono::time_point_cast<chrono::hours>(t0), utc)); | ||||
| } | ||||
| 
 | ||||
| TEST(Format, TimePointExtendedResolution) { | ||||
|   const char kFmt[] = "%H:%M:%E*S"; | ||||
|   const time_zone utc = utc_time_zone(); | ||||
|   const time_point<sys_seconds> tp = | ||||
|       std::chrono::time_point_cast<sys_seconds>( | ||||
|           std::chrono::system_clock::from_time_t(0)) + | ||||
|       std::chrono::hours(12) + std::chrono::minutes(34) + | ||||
|       std::chrono::seconds(56); | ||||
|   const time_point<absl::time_internal::cctz::seconds> tp = | ||||
|       chrono::time_point_cast<absl::time_internal::cctz::seconds>( | ||||
|           chrono::system_clock::from_time_t(0)) + | ||||
|       chrono::hours(12) + chrono::minutes(34) + chrono::seconds(56); | ||||
| 
 | ||||
|   EXPECT_EQ( | ||||
|       "12:34:56.123456789012345", | ||||
|  | @ -132,7 +127,7 @@ TEST(Format, TimePointExtendedResolution) { | |||
| 
 | ||||
| TEST(Format, Basics) { | ||||
|   time_zone tz = utc_time_zone(); | ||||
|   time_point<nanoseconds> tp = system_clock::from_time_t(0); | ||||
|   time_point<chrono::nanoseconds> tp = chrono::system_clock::from_time_t(0); | ||||
| 
 | ||||
|   // Starts with a couple basic edge cases.
 | ||||
|   EXPECT_EQ("", format("", tp, tz)); | ||||
|  | @ -145,8 +140,9 @@ TEST(Format, Basics) { | |||
|   std::string bigger(100000, 'x'); | ||||
|   EXPECT_EQ(bigger, format(bigger, tp, tz)); | ||||
| 
 | ||||
|   tp += hours(13) + minutes(4) + seconds(5); | ||||
|   tp += milliseconds(6) + microseconds(7) + nanoseconds(8); | ||||
|   tp += chrono::hours(13) + chrono::minutes(4) + chrono::seconds(5); | ||||
|   tp += chrono::milliseconds(6) + chrono::microseconds(7) + | ||||
|         chrono::nanoseconds(8); | ||||
|   EXPECT_EQ("1970-01-01", format("%Y-%m-%d", tp, tz)); | ||||
|   EXPECT_EQ("13:04:05", format("%H:%M:%S", tp, tz)); | ||||
|   EXPECT_EQ("13:04:05.006", format("%H:%M:%E3S", tp, tz)); | ||||
|  | @ -156,7 +152,7 @@ TEST(Format, Basics) { | |||
| 
 | ||||
| TEST(Format, PosixConversions) { | ||||
|   const time_zone tz = utc_time_zone(); | ||||
|   auto tp = system_clock::from_time_t(0); | ||||
|   auto tp = chrono::system_clock::from_time_t(0); | ||||
| 
 | ||||
|   TestFormatSpecifier(tp, tz, "%d", "01"); | ||||
|   TestFormatSpecifier(tp, tz, "%e", " 1");  // extension but internal support
 | ||||
|  | @ -196,7 +192,7 @@ TEST(Format, PosixConversions) { | |||
| 
 | ||||
| TEST(Format, LocaleSpecific) { | ||||
|   const time_zone tz = utc_time_zone(); | ||||
|   auto tp = system_clock::from_time_t(0); | ||||
|   auto tp = chrono::system_clock::from_time_t(0); | ||||
| 
 | ||||
|   TestFormatSpecifier(tp, tz, "%a", "Thu"); | ||||
|   TestFormatSpecifier(tp, tz, "%A", "Thursday"); | ||||
|  | @ -205,8 +201,8 @@ TEST(Format, LocaleSpecific) { | |||
| 
 | ||||
|   // %c should at least produce the numeric year and time-of-day.
 | ||||
|   const std::string s = format("%c", tp, utc_time_zone()); | ||||
|   EXPECT_THAT(s, HasSubstr("1970")); | ||||
|   EXPECT_THAT(s, HasSubstr("00:00:00")); | ||||
|   EXPECT_THAT(s, testing::HasSubstr("1970")); | ||||
|   EXPECT_THAT(s, testing::HasSubstr("00:00:00")); | ||||
| 
 | ||||
|   TestFormatSpecifier(tp, tz, "%p", "AM"); | ||||
|   TestFormatSpecifier(tp, tz, "%x", "01/01/70"); | ||||
|  | @ -245,7 +241,7 @@ TEST(Format, LocaleSpecific) { | |||
| 
 | ||||
| TEST(Format, Escaping) { | ||||
|   const time_zone tz = utc_time_zone(); | ||||
|   auto tp = system_clock::from_time_t(0); | ||||
|   auto tp = chrono::system_clock::from_time_t(0); | ||||
| 
 | ||||
|   TestFormatSpecifier(tp, tz, "%%", "%"); | ||||
|   TestFormatSpecifier(tp, tz, "%%a", "%a"); | ||||
|  | @ -266,8 +262,8 @@ TEST(Format, ExtendedSeconds) { | |||
|   const time_zone tz = utc_time_zone(); | ||||
| 
 | ||||
|   // No subseconds.
 | ||||
|   time_point<nanoseconds> tp = system_clock::from_time_t(0); | ||||
|   tp += seconds(5); | ||||
|   time_point<chrono::nanoseconds> tp = chrono::system_clock::from_time_t(0); | ||||
|   tp += chrono::seconds(5); | ||||
|   EXPECT_EQ("05", format("%E*S", tp, tz)); | ||||
|   EXPECT_EQ("05", format("%E0S", tp, tz)); | ||||
|   EXPECT_EQ("05.0", format("%E1S", tp, tz)); | ||||
|  | @ -287,7 +283,8 @@ TEST(Format, ExtendedSeconds) { | |||
|   EXPECT_EQ("05.000000000000000", format("%E15S", tp, tz)); | ||||
| 
 | ||||
|   // With subseconds.
 | ||||
|   tp += milliseconds(6) + microseconds(7) + nanoseconds(8); | ||||
|   tp += chrono::milliseconds(6) + chrono::microseconds(7) + | ||||
|         chrono::nanoseconds(8); | ||||
|   EXPECT_EQ("05.006007008", format("%E*S", tp, tz)); | ||||
|   EXPECT_EQ("05", format("%E0S", tp, tz)); | ||||
|   EXPECT_EQ("05.0", format("%E1S", tp, tz)); | ||||
|  | @ -307,17 +304,18 @@ TEST(Format, ExtendedSeconds) { | |||
|   EXPECT_EQ("05.006007008000000", format("%E15S", tp, tz)); | ||||
| 
 | ||||
|   // Times before the Unix epoch.
 | ||||
|   tp = system_clock::from_time_t(0) + microseconds(-1); | ||||
|   tp = chrono::system_clock::from_time_t(0) + chrono::microseconds(-1); | ||||
|   EXPECT_EQ("1969-12-31 23:59:59.999999", | ||||
|             format("%Y-%m-%d %H:%M:%E*S", tp, tz)); | ||||
| 
 | ||||
|   // Here is a "%E*S" case we got wrong for a while.  While the first
 | ||||
|   // instant below is correctly rendered as "...:07.333304", the second
 | ||||
|   // one used to appear as "...:07.33330499999999999".
 | ||||
|   tp = system_clock::from_time_t(0) + microseconds(1395024427333304); | ||||
|   tp = chrono::system_clock::from_time_t(0) + | ||||
|        chrono::microseconds(1395024427333304); | ||||
|   EXPECT_EQ("2014-03-17 02:47:07.333304", | ||||
|             format("%Y-%m-%d %H:%M:%E*S", tp, tz)); | ||||
|   tp += microseconds(1); | ||||
|   tp += chrono::microseconds(1); | ||||
|   EXPECT_EQ("2014-03-17 02:47:07.333305", | ||||
|             format("%Y-%m-%d %H:%M:%E*S", tp, tz)); | ||||
| } | ||||
|  | @ -326,8 +324,8 @@ TEST(Format, ExtendedSubeconds) { | |||
|   const time_zone tz = utc_time_zone(); | ||||
| 
 | ||||
|   // No subseconds.
 | ||||
|   time_point<nanoseconds> tp = system_clock::from_time_t(0); | ||||
|   tp += seconds(5); | ||||
|   time_point<chrono::nanoseconds> tp = chrono::system_clock::from_time_t(0); | ||||
|   tp += chrono::seconds(5); | ||||
|   EXPECT_EQ("0", format("%E*f", tp, tz)); | ||||
|   EXPECT_EQ("", format("%E0f", tp, tz)); | ||||
|   EXPECT_EQ("0", format("%E1f", tp, tz)); | ||||
|  | @ -347,7 +345,8 @@ TEST(Format, ExtendedSubeconds) { | |||
|   EXPECT_EQ("000000000000000", format("%E15f", tp, tz)); | ||||
| 
 | ||||
|   // With subseconds.
 | ||||
|   tp += milliseconds(6) + microseconds(7) + nanoseconds(8); | ||||
|   tp += chrono::milliseconds(6) + chrono::microseconds(7) + | ||||
|         chrono::nanoseconds(8); | ||||
|   EXPECT_EQ("006007008", format("%E*f", tp, tz)); | ||||
|   EXPECT_EQ("", format("%E0f", tp, tz)); | ||||
|   EXPECT_EQ("0", format("%E1f", tp, tz)); | ||||
|  | @ -367,17 +366,18 @@ TEST(Format, ExtendedSubeconds) { | |||
|   EXPECT_EQ("006007008000000", format("%E15f", tp, tz)); | ||||
| 
 | ||||
|   // Times before the Unix epoch.
 | ||||
|   tp = system_clock::from_time_t(0) + microseconds(-1); | ||||
|   tp = chrono::system_clock::from_time_t(0) + chrono::microseconds(-1); | ||||
|   EXPECT_EQ("1969-12-31 23:59:59.999999", | ||||
|             format("%Y-%m-%d %H:%M:%S.%E*f", tp, tz)); | ||||
| 
 | ||||
|   // Here is a "%E*S" case we got wrong for a while.  While the first
 | ||||
|   // instant below is correctly rendered as "...:07.333304", the second
 | ||||
|   // one used to appear as "...:07.33330499999999999".
 | ||||
|   tp = system_clock::from_time_t(0) + microseconds(1395024427333304); | ||||
|   tp = chrono::system_clock::from_time_t(0) + | ||||
|        chrono::microseconds(1395024427333304); | ||||
|   EXPECT_EQ("2014-03-17 02:47:07.333304", | ||||
|             format("%Y-%m-%d %H:%M:%S.%E*f", tp, tz)); | ||||
|   tp += microseconds(1); | ||||
|   tp += chrono::microseconds(1); | ||||
|   EXPECT_EQ("2014-03-17 02:47:07.333305", | ||||
|             format("%Y-%m-%d %H:%M:%S.%E*f", tp, tz)); | ||||
| } | ||||
|  | @ -392,8 +392,8 @@ TEST(Format, CompareExtendSecondsVsSubseconds) { | |||
|   auto fmt_B = [](const std::string& prec) { return "%S.%E" + prec + "f"; }; | ||||
| 
 | ||||
|   // No subseconds:
 | ||||
|   time_point<nanoseconds> tp = system_clock::from_time_t(0); | ||||
|   tp += seconds(5); | ||||
|   time_point<chrono::nanoseconds> tp = chrono::system_clock::from_time_t(0); | ||||
|   tp += chrono::seconds(5); | ||||
|   // ... %E*S and %S.%E*f are different.
 | ||||
|   EXPECT_EQ("05", format(fmt_A("*"), tp, tz)); | ||||
|   EXPECT_EQ("05.0", format(fmt_B("*"), tp, tz)); | ||||
|  | @ -409,7 +409,8 @@ TEST(Format, CompareExtendSecondsVsSubseconds) { | |||
| 
 | ||||
|   // With subseconds:
 | ||||
|   // ... %E*S and %S.%E*f are the same.
 | ||||
|   tp += milliseconds(6) + microseconds(7) + nanoseconds(8); | ||||
|   tp += chrono::milliseconds(6) + chrono::microseconds(7) + | ||||
|         chrono::nanoseconds(8); | ||||
|   EXPECT_EQ("05.006007008", format(fmt_A("*"), tp, tz)); | ||||
|   EXPECT_EQ("05.006007008", format(fmt_B("*"), tp, tz)); | ||||
|   // ... %E0S and %S.%E0f are different.
 | ||||
|  | @ -424,7 +425,7 @@ TEST(Format, CompareExtendSecondsVsSubseconds) { | |||
| } | ||||
| 
 | ||||
| TEST(Format, ExtendedOffset) { | ||||
|   auto tp = system_clock::from_time_t(0); | ||||
|   auto tp = chrono::system_clock::from_time_t(0); | ||||
| 
 | ||||
|   time_zone tz = utc_time_zone(); | ||||
|   TestFormatSpecifier(tp, tz, "%Ez", "+00:00"); | ||||
|  | @ -446,7 +447,7 @@ TEST(Format, ExtendedOffset) { | |||
| 
 | ||||
| TEST(Format, ExtendedSecondOffset) { | ||||
|   const time_zone utc = utc_time_zone(); | ||||
|   time_point<seconds> tp; | ||||
|   time_point<chrono::seconds> tp; | ||||
|   time_zone tz; | ||||
| 
 | ||||
|   EXPECT_TRUE(load_time_zone("America/New_York", &tz)); | ||||
|  | @ -458,7 +459,7 @@ TEST(Format, ExtendedSecondOffset) { | |||
|     TestFormatSpecifier(tp, tz, "%E*z", "-04:56:02"); | ||||
|     TestFormatSpecifier(tp, tz, "%Ez", "-04:56"); | ||||
|   } | ||||
|   tp += seconds(1); | ||||
|   tp += chrono::seconds(1); | ||||
|   TestFormatSpecifier(tp, tz, "%E*z", "-05:00:00"); | ||||
| 
 | ||||
|   EXPECT_TRUE(load_time_zone("Europe/Moscow", &tz)); | ||||
|  | @ -469,7 +470,7 @@ TEST(Format, ExtendedSecondOffset) { | |||
|   TestFormatSpecifier(tp, tz, "%E*z", "+04:31:19"); | ||||
|   TestFormatSpecifier(tp, tz, "%Ez", "+04:31"); | ||||
| #endif | ||||
|   tp += seconds(1); | ||||
|   tp += chrono::seconds(1); | ||||
|   TestFormatSpecifier(tp, tz, "%E*z", "+04:00:00"); | ||||
| } | ||||
| 
 | ||||
|  | @ -510,44 +511,44 @@ TEST(Format, RFC3339Format) { | |||
|   time_zone tz; | ||||
|   EXPECT_TRUE(load_time_zone("America/Los_Angeles", &tz)); | ||||
| 
 | ||||
|   time_point<nanoseconds> tp = | ||||
|   time_point<chrono::nanoseconds> tp = | ||||
|       convert(civil_second(1977, 6, 28, 9, 8, 7), tz); | ||||
|   EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_full, tp, tz)); | ||||
|   EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_sec, tp, tz)); | ||||
| 
 | ||||
|   tp += milliseconds(100); | ||||
|   tp += chrono::milliseconds(100); | ||||
|   EXPECT_EQ("1977-06-28T09:08:07.1-07:00", format(RFC3339_full, tp, tz)); | ||||
|   EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_sec, tp, tz)); | ||||
| 
 | ||||
|   tp += milliseconds(20); | ||||
|   tp += chrono::milliseconds(20); | ||||
|   EXPECT_EQ("1977-06-28T09:08:07.12-07:00", format(RFC3339_full, tp, tz)); | ||||
|   EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_sec, tp, tz)); | ||||
| 
 | ||||
|   tp += milliseconds(3); | ||||
|   tp += chrono::milliseconds(3); | ||||
|   EXPECT_EQ("1977-06-28T09:08:07.123-07:00", format(RFC3339_full, tp, tz)); | ||||
|   EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_sec, tp, tz)); | ||||
| 
 | ||||
|   tp += microseconds(400); | ||||
|   tp += chrono::microseconds(400); | ||||
|   EXPECT_EQ("1977-06-28T09:08:07.1234-07:00", format(RFC3339_full, tp, tz)); | ||||
|   EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_sec, tp, tz)); | ||||
| 
 | ||||
|   tp += microseconds(50); | ||||
|   tp += chrono::microseconds(50); | ||||
|   EXPECT_EQ("1977-06-28T09:08:07.12345-07:00", format(RFC3339_full, tp, tz)); | ||||
|   EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_sec, tp, tz)); | ||||
| 
 | ||||
|   tp += microseconds(6); | ||||
|   tp += chrono::microseconds(6); | ||||
|   EXPECT_EQ("1977-06-28T09:08:07.123456-07:00", format(RFC3339_full, tp, tz)); | ||||
|   EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_sec, tp, tz)); | ||||
| 
 | ||||
|   tp += nanoseconds(700); | ||||
|   tp += chrono::nanoseconds(700); | ||||
|   EXPECT_EQ("1977-06-28T09:08:07.1234567-07:00", format(RFC3339_full, tp, tz)); | ||||
|   EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_sec, tp, tz)); | ||||
| 
 | ||||
|   tp += nanoseconds(80); | ||||
|   tp += chrono::nanoseconds(80); | ||||
|   EXPECT_EQ("1977-06-28T09:08:07.12345678-07:00", format(RFC3339_full, tp, tz)); | ||||
|   EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_sec, tp, tz)); | ||||
| 
 | ||||
|   tp += nanoseconds(9); | ||||
|   tp += chrono::nanoseconds(9); | ||||
|   EXPECT_EQ("1977-06-28T09:08:07.123456789-07:00", | ||||
|             format(RFC3339_full, tp, tz)); | ||||
|   EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_sec, tp, tz)); | ||||
|  | @ -570,13 +571,13 @@ TEST(Parse, TimePointResolution) { | |||
|   const char kFmt[] = "%H:%M:%E*S"; | ||||
|   const time_zone utc = utc_time_zone(); | ||||
| 
 | ||||
|   time_point<nanoseconds> tp_ns; | ||||
|   time_point<chrono::nanoseconds> tp_ns; | ||||
|   EXPECT_TRUE(parse(kFmt, "03:04:05.123456789", utc, &tp_ns)); | ||||
|   EXPECT_EQ("03:04:05.123456789", format(kFmt, tp_ns, utc)); | ||||
|   EXPECT_TRUE(parse(kFmt, "03:04:05.123456", utc, &tp_ns)); | ||||
|   EXPECT_EQ("03:04:05.123456", format(kFmt, tp_ns, utc)); | ||||
| 
 | ||||
|   time_point<microseconds> tp_us; | ||||
|   time_point<chrono::microseconds> tp_us; | ||||
|   EXPECT_TRUE(parse(kFmt, "03:04:05.123456789", utc, &tp_us)); | ||||
|   EXPECT_EQ("03:04:05.123456", format(kFmt, tp_us, utc)); | ||||
|   EXPECT_TRUE(parse(kFmt, "03:04:05.123456", utc, &tp_us)); | ||||
|  | @ -584,7 +585,7 @@ TEST(Parse, TimePointResolution) { | |||
|   EXPECT_TRUE(parse(kFmt, "03:04:05.123", utc, &tp_us)); | ||||
|   EXPECT_EQ("03:04:05.123", format(kFmt, tp_us, utc)); | ||||
| 
 | ||||
|   time_point<milliseconds> tp_ms; | ||||
|   time_point<chrono::milliseconds> tp_ms; | ||||
|   EXPECT_TRUE(parse(kFmt, "03:04:05.123456", utc, &tp_ms)); | ||||
|   EXPECT_EQ("03:04:05.123", format(kFmt, tp_ms, utc)); | ||||
|   EXPECT_TRUE(parse(kFmt, "03:04:05.123", utc, &tp_ms)); | ||||
|  | @ -592,17 +593,17 @@ TEST(Parse, TimePointResolution) { | |||
|   EXPECT_TRUE(parse(kFmt, "03:04:05", utc, &tp_ms)); | ||||
|   EXPECT_EQ("03:04:05", format(kFmt, tp_ms, utc)); | ||||
| 
 | ||||
|   time_point<seconds> tp_s; | ||||
|   time_point<chrono::seconds> tp_s; | ||||
|   EXPECT_TRUE(parse(kFmt, "03:04:05.123", utc, &tp_s)); | ||||
|   EXPECT_EQ("03:04:05", format(kFmt, tp_s, utc)); | ||||
|   EXPECT_TRUE(parse(kFmt, "03:04:05", utc, &tp_s)); | ||||
|   EXPECT_EQ("03:04:05", format(kFmt, tp_s, utc)); | ||||
| 
 | ||||
|   time_point<minutes> tp_m; | ||||
|   time_point<chrono::minutes> tp_m; | ||||
|   EXPECT_TRUE(parse(kFmt, "03:04:05", utc, &tp_m)); | ||||
|   EXPECT_EQ("03:04:00", format(kFmt, tp_m, utc)); | ||||
| 
 | ||||
|   time_point<hours> tp_h; | ||||
|   time_point<chrono::hours> tp_h; | ||||
|   EXPECT_TRUE(parse(kFmt, "03:04:05", utc, &tp_h)); | ||||
|   EXPECT_EQ("03:00:00", format(kFmt, tp_h, utc)); | ||||
| } | ||||
|  | @ -611,7 +612,7 @@ TEST(Parse, TimePointExtendedResolution) { | |||
|   const char kFmt[] = "%H:%M:%E*S"; | ||||
|   const time_zone utc = utc_time_zone(); | ||||
| 
 | ||||
|   time_point<sys_seconds> tp; | ||||
|   time_point<absl::time_internal::cctz::seconds> tp; | ||||
|   detail::femtoseconds fs; | ||||
|   EXPECT_TRUE(detail::parse(kFmt, "12:34:56.123456789012345", utc, &tp, &fs)); | ||||
|   EXPECT_EQ("12:34:56.123456789012345", detail::format(kFmt, tp, fs, utc)); | ||||
|  | @ -629,11 +630,12 @@ TEST(Parse, TimePointExtendedResolution) { | |||
| 
 | ||||
| TEST(Parse, Basics) { | ||||
|   time_zone tz = utc_time_zone(); | ||||
|   time_point<nanoseconds> tp = system_clock::from_time_t(1234567890); | ||||
|   time_point<chrono::nanoseconds> tp = | ||||
|       chrono::system_clock::from_time_t(1234567890); | ||||
| 
 | ||||
|   // Simple edge cases.
 | ||||
|   EXPECT_TRUE(parse("", "", tz, &tp)); | ||||
|   EXPECT_EQ(system_clock::from_time_t(0), tp);  // everything defaulted
 | ||||
|   EXPECT_EQ(chrono::system_clock::from_time_t(0), tp);  // everything defaulted
 | ||||
|   EXPECT_TRUE(parse(" ", " ", tz, &tp)); | ||||
|   EXPECT_TRUE(parse("  ", "  ", tz, &tp)); | ||||
|   EXPECT_TRUE(parse("x", "x", tz, &tp)); | ||||
|  | @ -647,7 +649,7 @@ TEST(Parse, Basics) { | |||
| TEST(Parse, WithTimeZone) { | ||||
|   time_zone tz; | ||||
|   EXPECT_TRUE(load_time_zone("America/Los_Angeles", &tz)); | ||||
|   time_point<nanoseconds> tp; | ||||
|   time_point<chrono::nanoseconds> tp; | ||||
| 
 | ||||
|   // We can parse a std::string without a UTC offset if we supply a timezone.
 | ||||
|   EXPECT_TRUE(parse("%Y-%m-%d %H:%M:%S", "2013-06-28 19:08:09", tz, &tp)); | ||||
|  | @ -672,7 +674,7 @@ TEST(Parse, WithTimeZone) { | |||
| TEST(Parse, LeapSecond) { | ||||
|   time_zone tz; | ||||
|   EXPECT_TRUE(load_time_zone("America/Los_Angeles", &tz)); | ||||
|   time_point<nanoseconds> tp; | ||||
|   time_point<chrono::nanoseconds> tp; | ||||
| 
 | ||||
|   // ":59" -> ":59"
 | ||||
|   EXPECT_TRUE(parse(RFC3339_full, "2013-06-28T07:08:59-08:00", tz, &tp)); | ||||
|  | @ -696,7 +698,7 @@ TEST(Parse, LeapSecond) { | |||
| 
 | ||||
| TEST(Parse, ErrorCases) { | ||||
|   const time_zone tz = utc_time_zone(); | ||||
|   auto tp = system_clock::from_time_t(0); | ||||
|   auto tp = chrono::system_clock::from_time_t(0); | ||||
| 
 | ||||
|   // Illegal trailing data.
 | ||||
|   EXPECT_FALSE(parse("%S", "123", tz, &tp)); | ||||
|  | @ -739,7 +741,7 @@ TEST(Parse, ErrorCases) { | |||
| 
 | ||||
| TEST(Parse, PosixConversions) { | ||||
|   time_zone tz = utc_time_zone(); | ||||
|   auto tp = system_clock::from_time_t(0); | ||||
|   auto tp = chrono::system_clock::from_time_t(0); | ||||
|   const auto reset = convert(civil_second(1977, 6, 28, 9, 8, 7), tz); | ||||
| 
 | ||||
|   tp = reset; | ||||
|  | @ -828,14 +830,14 @@ TEST(Parse, PosixConversions) { | |||
| 
 | ||||
|   tp = reset; | ||||
|   EXPECT_TRUE(parse("%s", "1234567890", tz, &tp)); | ||||
|   EXPECT_EQ(system_clock::from_time_t(1234567890), tp); | ||||
|   EXPECT_EQ(chrono::system_clock::from_time_t(1234567890), tp); | ||||
| 
 | ||||
|   // %s conversion, like %z/%Ez, pays no heed to the optional zone.
 | ||||
|   time_zone lax; | ||||
|   EXPECT_TRUE(load_time_zone("America/Los_Angeles", &lax)); | ||||
|   tp = reset; | ||||
|   EXPECT_TRUE(parse("%s", "1234567890", lax, &tp)); | ||||
|   EXPECT_EQ(system_clock::from_time_t(1234567890), tp); | ||||
|   EXPECT_EQ(chrono::system_clock::from_time_t(1234567890), tp); | ||||
| 
 | ||||
|   // This is most important when the time has the same YMDhms
 | ||||
|   // breakdown in the zone as some other time.  For example, ...
 | ||||
|  | @ -843,16 +845,16 @@ TEST(Parse, PosixConversions) { | |||
|   //  1414920600 in US/Pacific -> Sun Nov 2 01:30:00 2014 (PST)
 | ||||
|   tp = reset; | ||||
|   EXPECT_TRUE(parse("%s", "1414917000", lax, &tp)); | ||||
|   EXPECT_EQ(system_clock::from_time_t(1414917000), tp); | ||||
|   EXPECT_EQ(chrono::system_clock::from_time_t(1414917000), tp); | ||||
|   tp = reset; | ||||
|   EXPECT_TRUE(parse("%s", "1414920600", lax, &tp)); | ||||
|   EXPECT_EQ(system_clock::from_time_t(1414920600), tp); | ||||
|   EXPECT_EQ(chrono::system_clock::from_time_t(1414920600), tp); | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| TEST(Parse, LocaleSpecific) { | ||||
|   time_zone tz = utc_time_zone(); | ||||
|   auto tp = system_clock::from_time_t(0); | ||||
|   auto tp = chrono::system_clock::from_time_t(0); | ||||
|   const auto reset = convert(civil_second(1977, 6, 28, 9, 8, 7), tz); | ||||
| 
 | ||||
|   // %a is parsed but ignored.
 | ||||
|  | @ -983,7 +985,8 @@ TEST(Parse, LocaleSpecific) { | |||
| 
 | ||||
| TEST(Parse, ExtendedSeconds) { | ||||
|   const time_zone tz = utc_time_zone(); | ||||
|   const time_point<nanoseconds> unix_epoch = system_clock::from_time_t(0); | ||||
|   const time_point<chrono::nanoseconds> unix_epoch = | ||||
|       chrono::system_clock::from_time_t(0); | ||||
| 
 | ||||
|   // All %E<prec>S cases are treated the same as %E*S on input.
 | ||||
|   auto precisions = {"*", "0", "1",  "2",  "3",  "4",  "5",  "6", "7", | ||||
|  | @ -991,47 +994,47 @@ TEST(Parse, ExtendedSeconds) { | |||
|   for (const std::string& prec : precisions) { | ||||
|     const std::string fmt = "%E" + prec + "S"; | ||||
|     SCOPED_TRACE(fmt); | ||||
|     time_point<nanoseconds> tp = unix_epoch; | ||||
|     time_point<chrono::nanoseconds> tp = unix_epoch; | ||||
|     EXPECT_TRUE(parse(fmt, "5", tz, &tp)); | ||||
|     EXPECT_EQ(unix_epoch + seconds(5), tp); | ||||
|     EXPECT_EQ(unix_epoch + chrono::seconds(5), tp); | ||||
|     tp = unix_epoch; | ||||
|     EXPECT_TRUE(parse(fmt, "05", tz, &tp)); | ||||
|     EXPECT_EQ(unix_epoch + seconds(5), tp); | ||||
|     EXPECT_EQ(unix_epoch + chrono::seconds(5), tp); | ||||
|     tp = unix_epoch; | ||||
|     EXPECT_TRUE(parse(fmt, "05.0", tz, &tp)); | ||||
|     EXPECT_EQ(unix_epoch + seconds(5), tp); | ||||
|     EXPECT_EQ(unix_epoch + chrono::seconds(5), tp); | ||||
|     tp = unix_epoch; | ||||
|     EXPECT_TRUE(parse(fmt, "05.00", tz, &tp)); | ||||
|     EXPECT_EQ(unix_epoch + seconds(5), tp); | ||||
|     EXPECT_EQ(unix_epoch + chrono::seconds(5), tp); | ||||
|     tp = unix_epoch; | ||||
|     EXPECT_TRUE(parse(fmt, "05.6", tz, &tp)); | ||||
|     EXPECT_EQ(unix_epoch + seconds(5) + milliseconds(600), tp); | ||||
|     EXPECT_EQ(unix_epoch + chrono::seconds(5) + chrono::milliseconds(600), tp); | ||||
|     tp = unix_epoch; | ||||
|     EXPECT_TRUE(parse(fmt, "05.60", tz, &tp)); | ||||
|     EXPECT_EQ(unix_epoch + seconds(5) + milliseconds(600), tp); | ||||
|     EXPECT_EQ(unix_epoch + chrono::seconds(5) + chrono::milliseconds(600), tp); | ||||
|     tp = unix_epoch; | ||||
|     EXPECT_TRUE(parse(fmt, "05.600", tz, &tp)); | ||||
|     EXPECT_EQ(unix_epoch + seconds(5) + milliseconds(600), tp); | ||||
|     EXPECT_EQ(unix_epoch + chrono::seconds(5) + chrono::milliseconds(600), tp); | ||||
|     tp = unix_epoch; | ||||
|     EXPECT_TRUE(parse(fmt, "05.67", tz, &tp)); | ||||
|     EXPECT_EQ(unix_epoch + seconds(5) + milliseconds(670), tp); | ||||
|     EXPECT_EQ(unix_epoch + chrono::seconds(5) + chrono::milliseconds(670), tp); | ||||
|     tp = unix_epoch; | ||||
|     EXPECT_TRUE(parse(fmt, "05.670", tz, &tp)); | ||||
|     EXPECT_EQ(unix_epoch + seconds(5) + milliseconds(670), tp); | ||||
|     EXPECT_EQ(unix_epoch + chrono::seconds(5) + chrono::milliseconds(670), tp); | ||||
|     tp = unix_epoch; | ||||
|     EXPECT_TRUE(parse(fmt, "05.678", tz, &tp)); | ||||
|     EXPECT_EQ(unix_epoch + seconds(5) + milliseconds(678), tp); | ||||
|     EXPECT_EQ(unix_epoch + chrono::seconds(5) + chrono::milliseconds(678), tp); | ||||
|   } | ||||
| 
 | ||||
|   // Here is a "%E*S" case we got wrong for a while.  The fractional
 | ||||
|   // part of the first instant is less than 2^31 and was correctly
 | ||||
|   // parsed, while the second (and any subsecond field >=2^31) failed.
 | ||||
|   time_point<nanoseconds> tp = unix_epoch; | ||||
|   time_point<chrono::nanoseconds> tp = unix_epoch; | ||||
|   EXPECT_TRUE(parse("%E*S", "0.2147483647", tz, &tp)); | ||||
|   EXPECT_EQ(unix_epoch + nanoseconds(214748364), tp); | ||||
|   EXPECT_EQ(unix_epoch + chrono::nanoseconds(214748364), tp); | ||||
|   tp = unix_epoch; | ||||
|   EXPECT_TRUE(parse("%E*S", "0.2147483648", tz, &tp)); | ||||
|   EXPECT_EQ(unix_epoch + nanoseconds(214748364), tp); | ||||
|   EXPECT_EQ(unix_epoch + chrono::nanoseconds(214748364), tp); | ||||
| 
 | ||||
|   // We should also be able to specify long strings of digits far
 | ||||
|   // beyond the current resolution and have them convert the same way.
 | ||||
|  | @ -1039,18 +1042,18 @@ TEST(Parse, ExtendedSeconds) { | |||
|   EXPECT_TRUE(parse( | ||||
|       "%E*S", "0.214748364801234567890123456789012345678901234567890123456789", | ||||
|       tz, &tp)); | ||||
|   EXPECT_EQ(unix_epoch + nanoseconds(214748364), tp); | ||||
|   EXPECT_EQ(unix_epoch + chrono::nanoseconds(214748364), tp); | ||||
| } | ||||
| 
 | ||||
| TEST(Parse, ExtendedSecondsScan) { | ||||
|   const time_zone tz = utc_time_zone(); | ||||
|   time_point<nanoseconds> tp; | ||||
|   time_point<chrono::nanoseconds> tp; | ||||
|   for (int ms = 0; ms < 1000; ms += 111) { | ||||
|     for (int us = 0; us < 1000; us += 27) { | ||||
|       const int micros = ms * 1000 + us; | ||||
|       for (int ns = 0; ns < 1000; ns += 9) { | ||||
|         const auto expected = | ||||
|             system_clock::from_time_t(0) + nanoseconds(micros * 1000 + ns); | ||||
|         const auto expected = chrono::system_clock::from_time_t(0) + | ||||
|                               chrono::nanoseconds(micros * 1000 + ns); | ||||
|         std::ostringstream oss; | ||||
|         oss << "0." << std::setfill('0') << std::setw(3); | ||||
|         oss << ms << std::setw(3) << us << std::setw(3) << ns; | ||||
|  | @ -1064,7 +1067,8 @@ TEST(Parse, ExtendedSecondsScan) { | |||
| 
 | ||||
| TEST(Parse, ExtendedSubeconds) { | ||||
|   const time_zone tz = utc_time_zone(); | ||||
|   const time_point<nanoseconds> unix_epoch = system_clock::from_time_t(0); | ||||
|   const time_point<chrono::nanoseconds> unix_epoch = | ||||
|       chrono::system_clock::from_time_t(0); | ||||
| 
 | ||||
|   // All %E<prec>f cases are treated the same as %E*f on input.
 | ||||
|   auto precisions = {"*", "0", "1",  "2",  "3",  "4",  "5",  "6", "7", | ||||
|  | @ -1072,41 +1076,42 @@ TEST(Parse, ExtendedSubeconds) { | |||
|   for (const std::string& prec : precisions) { | ||||
|     const std::string fmt = "%E" + prec + "f"; | ||||
|     SCOPED_TRACE(fmt); | ||||
|     time_point<nanoseconds> tp = unix_epoch - seconds(1); | ||||
|     time_point<chrono::nanoseconds> tp = unix_epoch - chrono::seconds(1); | ||||
|     EXPECT_TRUE(parse(fmt, "", tz, &tp)); | ||||
|     EXPECT_EQ(unix_epoch, tp); | ||||
|     tp = unix_epoch; | ||||
|     EXPECT_TRUE(parse(fmt, "6", tz, &tp)); | ||||
|     EXPECT_EQ(unix_epoch + milliseconds(600), tp); | ||||
|     EXPECT_EQ(unix_epoch + chrono::milliseconds(600), tp); | ||||
|     tp = unix_epoch; | ||||
|     EXPECT_TRUE(parse(fmt, "60", tz, &tp)); | ||||
|     EXPECT_EQ(unix_epoch + milliseconds(600), tp); | ||||
|     EXPECT_EQ(unix_epoch + chrono::milliseconds(600), tp); | ||||
|     tp = unix_epoch; | ||||
|     EXPECT_TRUE(parse(fmt, "600", tz, &tp)); | ||||
|     EXPECT_EQ(unix_epoch + milliseconds(600), tp); | ||||
|     EXPECT_EQ(unix_epoch + chrono::milliseconds(600), tp); | ||||
|     tp = unix_epoch; | ||||
|     EXPECT_TRUE(parse(fmt, "67", tz, &tp)); | ||||
|     EXPECT_EQ(unix_epoch + milliseconds(670), tp); | ||||
|     EXPECT_EQ(unix_epoch + chrono::milliseconds(670), tp); | ||||
|     tp = unix_epoch; | ||||
|     EXPECT_TRUE(parse(fmt, "670", tz, &tp)); | ||||
|     EXPECT_EQ(unix_epoch + milliseconds(670), tp); | ||||
|     EXPECT_EQ(unix_epoch + chrono::milliseconds(670), tp); | ||||
|     tp = unix_epoch; | ||||
|     EXPECT_TRUE(parse(fmt, "678", tz, &tp)); | ||||
|     EXPECT_EQ(unix_epoch + milliseconds(678), tp); | ||||
|     EXPECT_EQ(unix_epoch + chrono::milliseconds(678), tp); | ||||
|     tp = unix_epoch; | ||||
|     EXPECT_TRUE(parse(fmt, "6789", tz, &tp)); | ||||
|     EXPECT_EQ(unix_epoch + milliseconds(678) + microseconds(900), tp); | ||||
|     EXPECT_EQ( | ||||
|         unix_epoch + chrono::milliseconds(678) + chrono::microseconds(900), tp); | ||||
|   } | ||||
| 
 | ||||
|   // Here is a "%E*f" case we got wrong for a while.  The fractional
 | ||||
|   // part of the first instant is less than 2^31 and was correctly
 | ||||
|   // parsed, while the second (and any subsecond field >=2^31) failed.
 | ||||
|   time_point<nanoseconds> tp = unix_epoch; | ||||
|   time_point<chrono::nanoseconds> tp = unix_epoch; | ||||
|   EXPECT_TRUE(parse("%E*f", "2147483647", tz, &tp)); | ||||
|   EXPECT_EQ(unix_epoch + nanoseconds(214748364), tp); | ||||
|   EXPECT_EQ(unix_epoch + chrono::nanoseconds(214748364), tp); | ||||
|   tp = unix_epoch; | ||||
|   EXPECT_TRUE(parse("%E*f", "2147483648", tz, &tp)); | ||||
|   EXPECT_EQ(unix_epoch + nanoseconds(214748364), tp); | ||||
|   EXPECT_EQ(unix_epoch + chrono::nanoseconds(214748364), tp); | ||||
| 
 | ||||
|   // We should also be able to specify long strings of digits far
 | ||||
|   // beyond the current resolution and have them convert the same way.
 | ||||
|  | @ -1114,11 +1119,11 @@ TEST(Parse, ExtendedSubeconds) { | |||
|   EXPECT_TRUE(parse( | ||||
|       "%E*f", "214748364801234567890123456789012345678901234567890123456789", | ||||
|       tz, &tp)); | ||||
|   EXPECT_EQ(unix_epoch + nanoseconds(214748364), tp); | ||||
|   EXPECT_EQ(unix_epoch + chrono::nanoseconds(214748364), tp); | ||||
| } | ||||
| 
 | ||||
| TEST(Parse, ExtendedSubecondsScan) { | ||||
|   time_point<nanoseconds> tp; | ||||
|   time_point<chrono::nanoseconds> tp; | ||||
|   const time_zone tz = utc_time_zone(); | ||||
|   for (int ms = 0; ms < 1000; ms += 111) { | ||||
|     for (int us = 0; us < 1000; us += 27) { | ||||
|  | @ -1128,14 +1133,14 @@ TEST(Parse, ExtendedSubecondsScan) { | |||
|         oss << std::setfill('0') << std::setw(3) << ms; | ||||
|         oss << std::setw(3) << us << std::setw(3) << ns; | ||||
|         const std::string nanos = oss.str(); | ||||
|         const auto expected = | ||||
|             system_clock::from_time_t(0) + nanoseconds(micros * 1000 + ns); | ||||
|         const auto expected = chrono::system_clock::from_time_t(0) + | ||||
|                               chrono::nanoseconds(micros * 1000 + ns); | ||||
|         for (int ps = 0; ps < 1000; ps += 250) { | ||||
|           std::ostringstream oss; | ||||
|           oss << std::setfill('0') << std::setw(3) << ps; | ||||
|           const std::string input = nanos + oss.str() + "999"; | ||||
|           EXPECT_TRUE(parse("%E*f", input, tz, &tp)); | ||||
|           EXPECT_EQ(expected + nanoseconds(ps) / 1000, tp) << input; | ||||
|           EXPECT_EQ(expected + chrono::nanoseconds(ps) / 1000, tp) << input; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|  | @ -1144,7 +1149,7 @@ TEST(Parse, ExtendedSubecondsScan) { | |||
| 
 | ||||
| TEST(Parse, ExtendedOffset) { | ||||
|   const time_zone utc = utc_time_zone(); | ||||
|   time_point<sys_seconds> tp; | ||||
|   time_point<absl::time_internal::cctz::seconds> tp; | ||||
| 
 | ||||
|   // %z against +-HHMM.
 | ||||
|   EXPECT_TRUE(parse("%z", "+0000", utc, &tp)); | ||||
|  | @ -1194,7 +1199,7 @@ TEST(Parse, ExtendedOffset) { | |||
| 
 | ||||
| TEST(Parse, ExtendedSecondOffset) { | ||||
|   const time_zone utc = utc_time_zone(); | ||||
|   time_point<sys_seconds> tp; | ||||
|   time_point<absl::time_internal::cctz::seconds> tp; | ||||
| 
 | ||||
|   // %Ez against +-HH:MM:SS.
 | ||||
|   EXPECT_TRUE(parse("%Ez", "+00:00:00", utc, &tp)); | ||||
|  | @ -1263,7 +1268,7 @@ TEST(Parse, ExtendedSecondOffset) { | |||
| TEST(Parse, ExtendedYears) { | ||||
|   const time_zone utc = utc_time_zone(); | ||||
|   const char e4y_fmt[] = "%E4Y%m%d";  // no separators
 | ||||
|   time_point<sys_seconds> tp; | ||||
|   time_point<absl::time_internal::cctz::seconds> tp; | ||||
| 
 | ||||
|   // %E4Y consumes exactly four chars, including any sign.
 | ||||
|   EXPECT_TRUE(parse(e4y_fmt, "-9991127", utc, &tp)); | ||||
|  | @ -1294,45 +1299,45 @@ TEST(Parse, ExtendedYears) { | |||
| 
 | ||||
| TEST(Parse, RFC3339Format) { | ||||
|   const time_zone tz = utc_time_zone(); | ||||
|   time_point<nanoseconds> tp; | ||||
|   time_point<chrono::nanoseconds> tp; | ||||
|   EXPECT_TRUE(parse(RFC3339_sec, "2014-02-12T20:21:00+00:00", tz, &tp)); | ||||
|   ExpectTime(tp, tz, 2014, 2, 12, 20, 21, 0, 0, false, "UTC"); | ||||
| 
 | ||||
|   // Check that %Ez also accepts "Z" as a synonym for "+00:00".
 | ||||
|   time_point<nanoseconds> tp2; | ||||
|   time_point<chrono::nanoseconds> tp2; | ||||
|   EXPECT_TRUE(parse(RFC3339_sec, "2014-02-12T20:21:00Z", tz, &tp2)); | ||||
|   EXPECT_EQ(tp, tp2); | ||||
| } | ||||
| 
 | ||||
| TEST(Parse, MaxRange) { | ||||
|   const time_zone utc = utc_time_zone(); | ||||
|   time_point<sys_seconds> tp; | ||||
|   time_point<absl::time_internal::cctz::seconds> tp; | ||||
| 
 | ||||
|   // tests the upper limit using +00:00 offset
 | ||||
|   EXPECT_TRUE( | ||||
|       parse(RFC3339_sec, "292277026596-12-04T15:30:07+00:00", utc, &tp)); | ||||
|   EXPECT_EQ(tp, time_point<sys_seconds>::max()); | ||||
|   EXPECT_EQ(tp, time_point<absl::time_internal::cctz::seconds>::max()); | ||||
|   EXPECT_FALSE( | ||||
|       parse(RFC3339_sec, "292277026596-12-04T15:30:08+00:00", utc, &tp)); | ||||
| 
 | ||||
|   // tests the upper limit using -01:00 offset
 | ||||
|   EXPECT_TRUE( | ||||
|       parse(RFC3339_sec, "292277026596-12-04T14:30:07-01:00", utc, &tp)); | ||||
|   EXPECT_EQ(tp, time_point<sys_seconds>::max()); | ||||
|   EXPECT_EQ(tp, time_point<absl::time_internal::cctz::seconds>::max()); | ||||
|   EXPECT_FALSE( | ||||
|       parse(RFC3339_sec, "292277026596-12-04T15:30:07-01:00", utc, &tp)); | ||||
| 
 | ||||
|   // tests the lower limit using +00:00 offset
 | ||||
|   EXPECT_TRUE( | ||||
|       parse(RFC3339_sec, "-292277022657-01-27T08:29:52+00:00", utc, &tp)); | ||||
|   EXPECT_EQ(tp, time_point<sys_seconds>::min()); | ||||
|   EXPECT_EQ(tp, time_point<absl::time_internal::cctz::seconds>::min()); | ||||
|   EXPECT_FALSE( | ||||
|       parse(RFC3339_sec, "-292277022657-01-27T08:29:51+00:00", utc, &tp)); | ||||
| 
 | ||||
|   // tests the lower limit using +01:00 offset
 | ||||
|   EXPECT_TRUE( | ||||
|       parse(RFC3339_sec, "-292277022657-01-27T09:29:52+01:00", utc, &tp)); | ||||
|   EXPECT_EQ(tp, time_point<sys_seconds>::min()); | ||||
|   EXPECT_EQ(tp, time_point<absl::time_internal::cctz::seconds>::min()); | ||||
|   EXPECT_FALSE( | ||||
|       parse(RFC3339_sec, "-292277022657-01-27T08:29:51+01:00", utc, &tp)); | ||||
| 
 | ||||
|  | @ -1355,11 +1360,11 @@ TEST(FormatParse, RoundTrip) { | |||
|   time_zone lax; | ||||
|   EXPECT_TRUE(load_time_zone("America/Los_Angeles", &lax)); | ||||
|   const auto in = convert(civil_second(1977, 6, 28, 9, 8, 7), lax); | ||||
|   const auto subseconds = nanoseconds(654321); | ||||
|   const auto subseconds = chrono::nanoseconds(654321); | ||||
| 
 | ||||
|   // RFC3339, which renders subseconds.
 | ||||
|   { | ||||
|     time_point<nanoseconds> out; | ||||
|     time_point<chrono::nanoseconds> out; | ||||
|     const std::string s = format(RFC3339_full, in + subseconds, lax); | ||||
|     EXPECT_TRUE(parse(RFC3339_full, s, lax, &out)) << s; | ||||
|     EXPECT_EQ(in + subseconds, out);  // RFC3339_full includes %Ez
 | ||||
|  | @ -1367,7 +1372,7 @@ TEST(FormatParse, RoundTrip) { | |||
| 
 | ||||
|   // RFC1123, which only does whole seconds.
 | ||||
|   { | ||||
|     time_point<nanoseconds> out; | ||||
|     time_point<chrono::nanoseconds> out; | ||||
|     const std::string s = format(RFC1123_full, in, lax); | ||||
|     EXPECT_TRUE(parse(RFC1123_full, s, lax, &out)) << s; | ||||
|     EXPECT_EQ(in, out);  // RFC1123_full includes %z
 | ||||
|  | @ -1380,7 +1385,7 @@ TEST(FormatParse, RoundTrip) { | |||
|   // Even though we don't know what %c will produce, it should roundtrip,
 | ||||
|   // but only in the 0-offset timezone.
 | ||||
|   { | ||||
|     time_point<nanoseconds> out; | ||||
|     time_point<chrono::nanoseconds> out; | ||||
|     time_zone utc = utc_time_zone(); | ||||
|     const std::string s = format("%c", in, utc); | ||||
|     EXPECT_TRUE(parse("%c", s, utc, &out)) << s; | ||||
|  | @ -1391,18 +1396,18 @@ TEST(FormatParse, RoundTrip) { | |||
| 
 | ||||
| TEST(FormatParse, RoundTripDistantFuture) { | ||||
|   const time_zone utc = utc_time_zone(); | ||||
|   const time_point<sys_seconds> in = time_point<sys_seconds>::max(); | ||||
|   const time_point<absl::time_internal::cctz::seconds> in = time_point<absl::time_internal::cctz::seconds>::max(); | ||||
|   const std::string s = format(RFC3339_full, in, utc); | ||||
|   time_point<sys_seconds> out; | ||||
|   time_point<absl::time_internal::cctz::seconds> out; | ||||
|   EXPECT_TRUE(parse(RFC3339_full, s, utc, &out)) << s; | ||||
|   EXPECT_EQ(in, out); | ||||
| } | ||||
| 
 | ||||
| TEST(FormatParse, RoundTripDistantPast) { | ||||
|   const time_zone utc = utc_time_zone(); | ||||
|   const time_point<sys_seconds> in = time_point<sys_seconds>::min(); | ||||
|   const time_point<absl::time_internal::cctz::seconds> in = time_point<absl::time_internal::cctz::seconds>::min(); | ||||
|   const std::string s = format(RFC3339_full, in, utc); | ||||
|   time_point<sys_seconds> out; | ||||
|   time_point<absl::time_internal::cctz::seconds> out; | ||||
|   EXPECT_TRUE(parse(RFC3339_full, s, utc, &out)) << s; | ||||
|   EXPECT_EQ(in, out); | ||||
| } | ||||
|  |  | |||
|  | @ -37,30 +37,28 @@ class TimeZoneIf { | |||
|   virtual ~TimeZoneIf(); | ||||
| 
 | ||||
|   virtual time_zone::absolute_lookup BreakTime( | ||||
|       const time_point<sys_seconds>& tp) const = 0; | ||||
|       const time_point<seconds>& tp) const = 0; | ||||
|   virtual time_zone::civil_lookup MakeTime( | ||||
|       const civil_second& cs) const = 0; | ||||
| 
 | ||||
|   virtual std::string Description() const = 0; | ||||
|   virtual bool NextTransition(time_point<sys_seconds>* tp) const = 0; | ||||
|   virtual bool PrevTransition(time_point<sys_seconds>* tp) const = 0; | ||||
|   virtual bool NextTransition(time_point<seconds>* tp) const = 0; | ||||
|   virtual bool PrevTransition(time_point<seconds>* tp) const = 0; | ||||
| 
 | ||||
|  protected: | ||||
|   TimeZoneIf() {} | ||||
| }; | ||||
| 
 | ||||
| // Convert between time_point<sys_seconds> and a count of seconds since
 | ||||
| // the Unix epoch.  We assume that the std::chrono::system_clock and the
 | ||||
| // Convert between time_point<seconds> and a count of seconds since the
 | ||||
| // Unix epoch.  We assume that the std::chrono::system_clock and the
 | ||||
| // Unix clock are second aligned, but not that they share an epoch.
 | ||||
| inline std::int_fast64_t ToUnixSeconds(const time_point<sys_seconds>& tp) { | ||||
|   return (tp - std::chrono::time_point_cast<sys_seconds>( | ||||
|                    std::chrono::system_clock::from_time_t(0))) | ||||
|       .count(); | ||||
| inline std::int_fast64_t ToUnixSeconds(const time_point<seconds>& tp) { | ||||
|   return (tp - std::chrono::time_point_cast<seconds>( | ||||
|                    std::chrono::system_clock::from_time_t(0))).count(); | ||||
| } | ||||
| inline time_point<sys_seconds> FromUnixSeconds(std::int_fast64_t t) { | ||||
|   return std::chrono::time_point_cast<sys_seconds>( | ||||
|              std::chrono::system_clock::from_time_t(0)) + | ||||
|          sys_seconds(t); | ||||
| inline time_point<seconds> FromUnixSeconds(std::int_fast64_t t) { | ||||
|   return std::chrono::time_point_cast<seconds>( | ||||
|              std::chrono::system_clock::from_time_t(0)) + seconds(t); | ||||
| } | ||||
| 
 | ||||
| }  // namespace cctz
 | ||||
|  |  | |||
|  | @ -45,8 +45,8 @@ bool time_zone::Impl::LoadTimeZone(const std::string& name, time_zone* tz) { | |||
|   const time_zone::Impl* const utc_impl = UTCImpl(); | ||||
| 
 | ||||
|   // First check for UTC (which is never a key in time_zone_map).
 | ||||
|   auto offset = sys_seconds::zero(); | ||||
|   if (FixedOffsetFromName(name, &offset) && offset == sys_seconds::zero()) { | ||||
|   auto offset = seconds::zero(); | ||||
|   if (FixedOffsetFromName(name, &offset) && offset == seconds::zero()) { | ||||
|     *tz = time_zone(utc_impl); | ||||
|     return true; | ||||
|   } | ||||
|  |  | |||
|  | @ -48,8 +48,7 @@ class time_zone::Impl { | |||
|   const std::string& name() const { return name_; } | ||||
| 
 | ||||
|   // Breaks a time_point down to civil-time components in this time zone.
 | ||||
|   time_zone::absolute_lookup BreakTime( | ||||
|       const time_point<sys_seconds>& tp) const { | ||||
|   time_zone::absolute_lookup BreakTime(const time_point<seconds>& tp) const { | ||||
|     return zone_->BreakTime(tp); | ||||
|   } | ||||
| 
 | ||||
|  | @ -75,10 +74,10 @@ class time_zone::Impl { | |||
|   // to NextTransition()/PrevTransition() will eventually return false,
 | ||||
|   // but it is unspecified exactly when NextTransition(&tp) jumps to false,
 | ||||
|   // or what time is set by PrevTransition(&tp) for a very distant tp.
 | ||||
|   bool NextTransition(time_point<sys_seconds>* tp) const { | ||||
|   bool NextTransition(time_point<seconds>* tp) const { | ||||
|     return zone_->NextTransition(tp); | ||||
|   } | ||||
|   bool PrevTransition(time_point<sys_seconds>* tp) const { | ||||
|   bool PrevTransition(time_point<seconds>* tp) const { | ||||
|     return zone_->PrevTransition(tp); | ||||
|   } | ||||
| 
 | ||||
|  |  | |||
|  | @ -140,7 +140,7 @@ std::int_fast64_t TransOffset(bool leap_year, int jan1_weekday, | |||
|   return (days * kSecsPerDay) + pt.time.offset; | ||||
| } | ||||
| 
 | ||||
| inline time_zone::civil_lookup MakeUnique(const time_point<sys_seconds>& tp) { | ||||
| inline time_zone::civil_lookup MakeUnique(const time_point<seconds>& tp) { | ||||
|   time_zone::civil_lookup cl; | ||||
|   cl.kind = time_zone::civil_lookup::UNIQUE; | ||||
|   cl.pre = cl.trans = cl.post = tp; | ||||
|  | @ -179,7 +179,7 @@ inline civil_second YearShift(const civil_second& cs, year_t shift) { | |||
| }  // namespace
 | ||||
| 
 | ||||
| // What (no leap-seconds) UTC+seconds zoneinfo would look like.
 | ||||
| bool TimeZoneInfo::ResetToBuiltinUTC(const sys_seconds& offset) { | ||||
| bool TimeZoneInfo::ResetToBuiltinUTC(const seconds& offset) { | ||||
|   transition_types_.resize(1); | ||||
|   TransitionType& tt(transition_types_.back()); | ||||
|   tt.utc_offset = static_cast<std::int_least32_t>(offset.count()); | ||||
|  | @ -218,8 +218,8 @@ bool TimeZoneInfo::ResetToBuiltinUTC(const sys_seconds& offset) { | |||
|   future_spec_.clear();  // never needed for a fixed-offset zone
 | ||||
|   extended_ = false; | ||||
| 
 | ||||
|   tt.civil_max = LocalTime(sys_seconds::max().count(), tt).cs; | ||||
|   tt.civil_min = LocalTime(sys_seconds::min().count(), tt).cs; | ||||
|   tt.civil_max = LocalTime(seconds::max().count(), tt).cs; | ||||
|   tt.civil_min = LocalTime(seconds::min().count(), tt).cs; | ||||
| 
 | ||||
|   transitions_.shrink_to_fit(); | ||||
|   return true; | ||||
|  | @ -565,10 +565,10 @@ bool TimeZoneInfo::Load(const std::string& name, ZoneInfoSource* zip) { | |||
|   } | ||||
| 
 | ||||
|   // Compute the maximum/minimum civil times that can be converted to a
 | ||||
|   // time_point<sys_seconds> for each of the zone's transition types.
 | ||||
|   // time_point<seconds> for each of the zone's transition types.
 | ||||
|   for (auto& tt : transition_types_) { | ||||
|     tt.civil_max = LocalTime(sys_seconds::max().count(), tt).cs; | ||||
|     tt.civil_min = LocalTime(sys_seconds::min().count(), tt).cs; | ||||
|     tt.civil_max = LocalTime(seconds::max().count(), tt).cs; | ||||
|     tt.civil_min = LocalTime(seconds::min().count(), tt).cs; | ||||
|   } | ||||
| 
 | ||||
|   transitions_.shrink_to_fit(); | ||||
|  | @ -713,7 +713,7 @@ bool TimeZoneInfo::Load(const std::string& name) { | |||
|   // zone never fails because the simple, fixed-offset state can be
 | ||||
|   // internally generated. Note that this depends on our choice to not
 | ||||
|   // accept leap-second encoded ("right") zoneinfo.
 | ||||
|   auto offset = sys_seconds::zero(); | ||||
|   auto offset = seconds::zero(); | ||||
|   if (FixedOffsetFromName(name, &offset)) { | ||||
|     return ResetToBuiltinUTC(offset); | ||||
|   } | ||||
|  | @ -755,14 +755,14 @@ time_zone::civil_lookup TimeZoneInfo::TimeLocal(const civil_second& cs, | |||
|                                                 year_t c4_shift) const { | ||||
|   assert(last_year_ - 400 < cs.year() && cs.year() <= last_year_); | ||||
|   time_zone::civil_lookup cl = MakeTime(cs); | ||||
|   if (c4_shift > sys_seconds::max().count() / kSecsPer400Years) { | ||||
|     cl.pre = cl.trans = cl.post = time_point<sys_seconds>::max(); | ||||
|   if (c4_shift > seconds::max().count() / kSecsPer400Years) { | ||||
|     cl.pre = cl.trans = cl.post = time_point<seconds>::max(); | ||||
|   } else { | ||||
|     const auto offset = sys_seconds(c4_shift * kSecsPer400Years); | ||||
|     const auto limit = time_point<sys_seconds>::max() - offset; | ||||
|     const auto offset = seconds(c4_shift * kSecsPer400Years); | ||||
|     const auto limit = time_point<seconds>::max() - offset; | ||||
|     for (auto* tp : {&cl.pre, &cl.trans, &cl.post}) { | ||||
|       if (*tp > limit) { | ||||
|         *tp = time_point<sys_seconds>::max(); | ||||
|         *tp = time_point<seconds>::max(); | ||||
|       } else { | ||||
|         *tp += offset; | ||||
|       } | ||||
|  | @ -772,7 +772,7 @@ time_zone::civil_lookup TimeZoneInfo::TimeLocal(const civil_second& cs, | |||
| } | ||||
| 
 | ||||
| time_zone::absolute_lookup TimeZoneInfo::BreakTime( | ||||
|     const time_point<sys_seconds>& tp) const { | ||||
|     const time_point<seconds>& tp) const { | ||||
|   std::int_fast64_t unix_time = ToUnixSeconds(tp); | ||||
|   const std::size_t timecnt = transitions_.size(); | ||||
|   assert(timecnt != 0);  // We always add a transition.
 | ||||
|  | @ -788,7 +788,7 @@ time_zone::absolute_lookup TimeZoneInfo::BreakTime( | |||
|       const std::int_fast64_t diff = | ||||
|           unix_time - transitions_[timecnt - 1].unix_time; | ||||
|       const year_t shift = diff / kSecsPer400Years + 1; | ||||
|       const auto d = sys_seconds(shift * kSecsPer400Years); | ||||
|       const auto d = seconds(shift * kSecsPer400Years); | ||||
|       time_zone::absolute_lookup al = BreakTime(tp - d); | ||||
|       al.cs = YearShift(al.cs, shift * 400); | ||||
|       return al; | ||||
|  | @ -847,7 +847,7 @@ time_zone::civil_lookup TimeZoneInfo::MakeTime(const civil_second& cs) const { | |||
|     if (tr->prev_civil_sec >= cs) { | ||||
|       // Before first transition, so use the default offset.
 | ||||
|       const TransitionType& tt(transition_types_[default_transition_type_]); | ||||
|       if (cs < tt.civil_min) return MakeUnique(time_point<sys_seconds>::min()); | ||||
|       if (cs < tt.civil_min) return MakeUnique(time_point<seconds>::min()); | ||||
|       return MakeUnique(cs - (civil_second() + tt.utc_offset)); | ||||
|     } | ||||
|     // tr->prev_civil_sec < cs < tr->civil_sec
 | ||||
|  | @ -864,7 +864,7 @@ time_zone::civil_lookup TimeZoneInfo::MakeTime(const civil_second& cs) const { | |||
|         return TimeLocal(YearShift(cs, shift * -400), shift); | ||||
|       } | ||||
|       const TransitionType& tt(transition_types_[tr->type_index]); | ||||
|       if (cs > tt.civil_max) return MakeUnique(time_point<sys_seconds>::max()); | ||||
|       if (cs > tt.civil_max) return MakeUnique(time_point<seconds>::max()); | ||||
|       return MakeUnique(tr->unix_time + (cs - tr->civil_sec)); | ||||
|     } | ||||
|     // tr->civil_sec <= cs <= tr->prev_civil_sec
 | ||||
|  | @ -895,7 +895,7 @@ std::string TimeZoneInfo::Description() const { | |||
|   return oss.str(); | ||||
| } | ||||
| 
 | ||||
| bool TimeZoneInfo::NextTransition(time_point<sys_seconds>* tp) const { | ||||
| bool TimeZoneInfo::NextTransition(time_point<seconds>* tp) const { | ||||
|   if (transitions_.empty()) return false; | ||||
|   const Transition* begin = &transitions_[0]; | ||||
|   const Transition* end = begin + transitions_.size(); | ||||
|  | @ -919,7 +919,7 @@ bool TimeZoneInfo::NextTransition(time_point<sys_seconds>* tp) const { | |||
|   return true; | ||||
| } | ||||
| 
 | ||||
| bool TimeZoneInfo::PrevTransition(time_point<sys_seconds>* tp) const { | ||||
| bool TimeZoneInfo::PrevTransition(time_point<seconds>* tp) const { | ||||
|   if (transitions_.empty()) return false; | ||||
|   const Transition* begin = &transitions_[0]; | ||||
|   const Transition* end = begin + transitions_.size(); | ||||
|  |  | |||
|  | @ -71,12 +71,12 @@ class TimeZoneInfo : public TimeZoneIf { | |||
| 
 | ||||
|   // TimeZoneIf implementations.
 | ||||
|   time_zone::absolute_lookup BreakTime( | ||||
|       const time_point<sys_seconds>& tp) const override; | ||||
|       const time_point<seconds>& tp) const override; | ||||
|   time_zone::civil_lookup MakeTime( | ||||
|       const civil_second& cs) const override; | ||||
|   std::string Description() const override; | ||||
|   bool NextTransition(time_point<sys_seconds>* tp) const override; | ||||
|   bool PrevTransition(time_point<sys_seconds>* tp) const override; | ||||
|   bool NextTransition(time_point<seconds>* tp) const override; | ||||
|   bool PrevTransition(time_point<seconds>* tp) const override; | ||||
| 
 | ||||
|  private: | ||||
|   struct Header {  // counts of:
 | ||||
|  | @ -98,7 +98,7 @@ class TimeZoneInfo : public TimeZoneIf { | |||
|                         std::uint_fast8_t tt2_index) const; | ||||
|   void ExtendTransitions(const std::string& name, const Header& hdr); | ||||
| 
 | ||||
|   bool ResetToBuiltinUTC(const sys_seconds& offset); | ||||
|   bool ResetToBuiltinUTC(const seconds& offset); | ||||
|   bool Load(const std::string& name, ZoneInfoSource* zip); | ||||
| 
 | ||||
|   // Helpers for BreakTime() and MakeTime().
 | ||||
|  |  | |||
|  | @ -91,7 +91,7 @@ TimeZoneLibC::TimeZoneLibC(const std::string& name) | |||
|     : local_(name == "localtime") {} | ||||
| 
 | ||||
| time_zone::absolute_lookup TimeZoneLibC::BreakTime( | ||||
|     const time_point<sys_seconds>& tp) const { | ||||
|     const time_point<seconds>& tp) const { | ||||
|   time_zone::absolute_lookup al; | ||||
|   std::time_t t = ToUnixSeconds(tp); | ||||
|   std::tm tm; | ||||
|  | @ -143,11 +143,11 @@ std::string TimeZoneLibC::Description() const { | |||
|   return local_ ? "localtime" : "UTC"; | ||||
| } | ||||
| 
 | ||||
| bool TimeZoneLibC::NextTransition(time_point<sys_seconds>* tp) const { | ||||
| bool TimeZoneLibC::NextTransition(time_point<seconds>* tp) const { | ||||
|   return false; | ||||
| } | ||||
| 
 | ||||
| bool TimeZoneLibC::PrevTransition(time_point<sys_seconds>* tp) const { | ||||
| bool TimeZoneLibC::PrevTransition(time_point<seconds>* tp) const { | ||||
|   return false; | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -32,12 +32,12 @@ class TimeZoneLibC : public TimeZoneIf { | |||
| 
 | ||||
|   // TimeZoneIf implementations.
 | ||||
|   time_zone::absolute_lookup BreakTime( | ||||
|       const time_point<sys_seconds>& tp) const override; | ||||
|       const time_point<seconds>& tp) const override; | ||||
|   time_zone::civil_lookup MakeTime( | ||||
|       const civil_second& cs) const override; | ||||
|   std::string Description() const override; | ||||
|   bool NextTransition(time_point<sys_seconds>* tp) const override; | ||||
|   bool PrevTransition(time_point<sys_seconds>* tp) const override; | ||||
|   bool NextTransition(time_point<seconds>* tp) const override; | ||||
|   bool PrevTransition(time_point<seconds>* tp) const override; | ||||
| 
 | ||||
|  private: | ||||
|   const bool local_;  // localtime or UTC
 | ||||
|  |  | |||
|  | @ -65,7 +65,7 @@ std::string time_zone::name() const { | |||
| } | ||||
| 
 | ||||
| time_zone::absolute_lookup time_zone::lookup( | ||||
|     const time_point<sys_seconds>& tp) const { | ||||
|     const time_point<seconds>& tp) const { | ||||
|   return time_zone::Impl::get(*this).BreakTime(tp); | ||||
| } | ||||
| 
 | ||||
|  | @ -85,7 +85,7 @@ time_zone utc_time_zone() { | |||
|   return time_zone::Impl::UTC();  // avoid name lookup
 | ||||
| } | ||||
| 
 | ||||
| time_zone fixed_time_zone(const sys_seconds& offset) { | ||||
| time_zone fixed_time_zone(const seconds& offset) { | ||||
|   time_zone tz; | ||||
|   load_time_zone(FixedOffsetToName(offset), &tz); | ||||
|   return tz; | ||||
|  |  | |||
|  | @ -24,14 +24,7 @@ | |||
| #include "absl/time/internal/cctz/include/cctz/civil_time.h" | ||||
| #include "gtest/gtest.h" | ||||
| 
 | ||||
| using std::chrono::time_point_cast; | ||||
| using std::chrono::system_clock; | ||||
| using std::chrono::nanoseconds; | ||||
| using std::chrono::microseconds; | ||||
| using std::chrono::milliseconds; | ||||
| using std::chrono::seconds; | ||||
| using std::chrono::minutes; | ||||
| using std::chrono::hours; | ||||
| namespace chrono = std::chrono; | ||||
| 
 | ||||
| namespace absl { | ||||
| namespace time_internal { | ||||
|  | @ -715,13 +708,13 @@ TEST(TimeZone, NamedTimeZones) { | |||
|   EXPECT_EQ("America/New_York", nyc.name()); | ||||
|   const time_zone syd = LoadZone("Australia/Sydney"); | ||||
|   EXPECT_EQ("Australia/Sydney", syd.name()); | ||||
|   const time_zone fixed0 = fixed_time_zone(sys_seconds::zero()); | ||||
|   const time_zone fixed0 = fixed_time_zone(absl::time_internal::cctz::seconds::zero()); | ||||
|   EXPECT_EQ("UTC", fixed0.name()); | ||||
|   const time_zone fixed_pos = | ||||
|       fixed_time_zone(hours(3) + minutes(25) + seconds(45)); | ||||
|   const time_zone fixed_pos = fixed_time_zone( | ||||
|       chrono::hours(3) + chrono::minutes(25) + chrono::seconds(45)); | ||||
|   EXPECT_EQ("Fixed/UTC+03:25:45", fixed_pos.name()); | ||||
|   const time_zone fixed_neg = | ||||
|       fixed_time_zone(-(hours(12) + minutes(34) + seconds(56))); | ||||
|   const time_zone fixed_neg = fixed_time_zone( | ||||
|       -(chrono::hours(12) + chrono::minutes(34) + chrono::seconds(56))); | ||||
|   EXPECT_EQ("Fixed/UTC-12:34:56", fixed_neg.name()); | ||||
| } | ||||
| 
 | ||||
|  | @ -731,19 +724,19 @@ TEST(TimeZone, Failures) { | |||
| 
 | ||||
|   tz = LoadZone("America/Los_Angeles"); | ||||
|   EXPECT_FALSE(load_time_zone("Invalid/TimeZone", &tz)); | ||||
|   EXPECT_EQ(system_clock::from_time_t(0), | ||||
|   EXPECT_EQ(chrono::system_clock::from_time_t(0), | ||||
|             convert(civil_second(1970, 1, 1, 0, 0, 0), tz));  // UTC
 | ||||
| 
 | ||||
|   // Ensures that the load still fails on a subsequent attempt.
 | ||||
|   tz = LoadZone("America/Los_Angeles"); | ||||
|   EXPECT_FALSE(load_time_zone("Invalid/TimeZone", &tz)); | ||||
|   EXPECT_EQ(system_clock::from_time_t(0), | ||||
|   EXPECT_EQ(chrono::system_clock::from_time_t(0), | ||||
|             convert(civil_second(1970, 1, 1, 0, 0, 0), tz));  // UTC
 | ||||
| 
 | ||||
|   // Loading an empty std::string timezone should fail.
 | ||||
|   tz = LoadZone("America/Los_Angeles"); | ||||
|   EXPECT_FALSE(load_time_zone("", &tz)); | ||||
|   EXPECT_EQ(system_clock::from_time_t(0), | ||||
|   EXPECT_EQ(chrono::system_clock::from_time_t(0), | ||||
|             convert(civil_second(1970, 1, 1, 0, 0, 0), tz));  // UTC
 | ||||
| } | ||||
| 
 | ||||
|  | @ -758,7 +751,7 @@ TEST(TimeZone, Equality) { | |||
|   EXPECT_EQ(implicit_utc, explicit_utc); | ||||
|   EXPECT_EQ(implicit_utc.name(), explicit_utc.name()); | ||||
| 
 | ||||
|   const time_zone fixed_zero = fixed_time_zone(sys_seconds::zero()); | ||||
|   const time_zone fixed_zero = fixed_time_zone(absl::time_internal::cctz::seconds::zero()); | ||||
|   EXPECT_EQ(fixed_zero, LoadZone(fixed_zero.name())); | ||||
|   EXPECT_EQ(fixed_zero, explicit_utc); | ||||
| 
 | ||||
|  | @ -766,23 +759,25 @@ TEST(TimeZone, Equality) { | |||
|   EXPECT_EQ(fixed_utc, LoadZone(fixed_utc.name())); | ||||
|   EXPECT_EQ(fixed_utc, explicit_utc); | ||||
| 
 | ||||
|   const time_zone fixed_pos = | ||||
|       fixed_time_zone(hours(3) + minutes(25) + seconds(45)); | ||||
|   const time_zone fixed_pos = fixed_time_zone( | ||||
|       chrono::hours(3) + chrono::minutes(25) + chrono::seconds(45)); | ||||
|   EXPECT_EQ(fixed_pos, LoadZone(fixed_pos.name())); | ||||
|   EXPECT_NE(fixed_pos, explicit_utc); | ||||
|   const time_zone fixed_neg = | ||||
|       fixed_time_zone(-(hours(12) + minutes(34) + seconds(56))); | ||||
|   const time_zone fixed_neg = fixed_time_zone( | ||||
|       -(chrono::hours(12) + chrono::minutes(34) + chrono::seconds(56))); | ||||
|   EXPECT_EQ(fixed_neg, LoadZone(fixed_neg.name())); | ||||
|   EXPECT_NE(fixed_neg, explicit_utc); | ||||
| 
 | ||||
|   const time_zone fixed_lim = fixed_time_zone(hours(24)); | ||||
|   const time_zone fixed_lim = fixed_time_zone(chrono::hours(24)); | ||||
|   EXPECT_EQ(fixed_lim, LoadZone(fixed_lim.name())); | ||||
|   EXPECT_NE(fixed_lim, explicit_utc); | ||||
|   const time_zone fixed_ovfl = fixed_time_zone(hours(24) + seconds(1)); | ||||
|   const time_zone fixed_ovfl = | ||||
|       fixed_time_zone(chrono::hours(24) + chrono::seconds(1)); | ||||
|   EXPECT_EQ(fixed_ovfl, LoadZone(fixed_ovfl.name())); | ||||
|   EXPECT_EQ(fixed_ovfl, explicit_utc); | ||||
| 
 | ||||
|   EXPECT_EQ(fixed_time_zone(seconds(1)), fixed_time_zone(seconds(1))); | ||||
|   EXPECT_EQ(fixed_time_zone(chrono::seconds(1)), | ||||
|             fixed_time_zone(chrono::seconds(1))); | ||||
| 
 | ||||
|   const time_zone local = local_time_zone(); | ||||
|   EXPECT_EQ(local, LoadZone(local.name())); | ||||
|  | @ -795,40 +790,43 @@ TEST(TimeZone, Equality) { | |||
| TEST(StdChronoTimePoint, TimeTAlignment) { | ||||
|   // Ensures that the Unix epoch and the system clock epoch are an integral
 | ||||
|   // number of seconds apart. This simplifies conversions to/from time_t.
 | ||||
|   auto diff = system_clock::time_point() - system_clock::from_time_t(0); | ||||
|   EXPECT_EQ(system_clock::time_point::duration::zero(), diff % seconds(1)); | ||||
|   auto diff = chrono::system_clock::time_point() - | ||||
|               chrono::system_clock::from_time_t(0); | ||||
|   EXPECT_EQ(chrono::system_clock::time_point::duration::zero(), | ||||
|             diff % chrono::seconds(1)); | ||||
| } | ||||
| 
 | ||||
| TEST(BreakTime, TimePointResolution) { | ||||
|   const time_zone utc = utc_time_zone(); | ||||
|   const auto t0 = system_clock::from_time_t(0); | ||||
|   const auto t0 = chrono::system_clock::from_time_t(0); | ||||
| 
 | ||||
|   ExpectTime(time_point_cast<nanoseconds>(t0), utc, | ||||
|   ExpectTime(chrono::time_point_cast<chrono::nanoseconds>(t0), utc, | ||||
|              1970, 1, 1, 0, 0, 0, 0, false, "UTC"); | ||||
|   ExpectTime(time_point_cast<microseconds>(t0), utc, | ||||
|   ExpectTime(chrono::time_point_cast<chrono::microseconds>(t0), utc, | ||||
|              1970, 1, 1, 0, 0, 0, 0, false, "UTC"); | ||||
|   ExpectTime(time_point_cast<milliseconds>(t0), utc, | ||||
|   ExpectTime(chrono::time_point_cast<chrono::milliseconds>(t0), utc, | ||||
|              1970, 1, 1, 0, 0, 0, 0, false, "UTC"); | ||||
|   ExpectTime(time_point_cast<seconds>(t0), utc, | ||||
|   ExpectTime(chrono::time_point_cast<chrono::seconds>(t0), utc, | ||||
|              1970, 1, 1, 0, 0, 0, 0, false, "UTC"); | ||||
|   ExpectTime(time_point_cast<sys_seconds>(t0), utc, | ||||
|   ExpectTime(chrono::time_point_cast<absl::time_internal::cctz::seconds>(t0), utc, | ||||
|              1970, 1, 1, 0, 0, 0, 0, false, "UTC"); | ||||
|   ExpectTime(time_point_cast<minutes>(t0), utc, | ||||
|   ExpectTime(chrono::time_point_cast<chrono::minutes>(t0), utc, | ||||
|              1970, 1, 1, 0, 0, 0, 0, false, "UTC"); | ||||
|   ExpectTime(time_point_cast<hours>(t0), utc, | ||||
|   ExpectTime(chrono::time_point_cast<chrono::hours>(t0), utc, | ||||
|              1970, 1, 1, 0, 0, 0, 0, false, "UTC"); | ||||
| } | ||||
| 
 | ||||
| TEST(BreakTime, LocalTimeInUTC) { | ||||
|   const time_zone tz = utc_time_zone(); | ||||
|   const auto tp = system_clock::from_time_t(0); | ||||
|   const auto tp = chrono::system_clock::from_time_t(0); | ||||
|   ExpectTime(tp, tz, 1970, 1, 1, 0, 0, 0, 0, false, "UTC"); | ||||
|   EXPECT_EQ(weekday::thursday, get_weekday(civil_day(convert(tp, tz)))); | ||||
| } | ||||
| 
 | ||||
| TEST(BreakTime, LocalTimeInUTCUnaligned) { | ||||
|   const time_zone tz = utc_time_zone(); | ||||
|   const auto tp = system_clock::from_time_t(0) - milliseconds(500); | ||||
|   const auto tp = | ||||
|       chrono::system_clock::from_time_t(0) - chrono::milliseconds(500); | ||||
|   ExpectTime(tp, tz, 1969, 12, 31, 23, 59, 59, 0, false, "UTC"); | ||||
|   EXPECT_EQ(weekday::wednesday, get_weekday(civil_day(convert(tp, tz)))); | ||||
| } | ||||
|  | @ -836,15 +834,16 @@ TEST(BreakTime, LocalTimeInUTCUnaligned) { | |||
| TEST(BreakTime, LocalTimePosix) { | ||||
|   // See IEEE Std 1003.1-1988 B.2.3 General Terms, Epoch.
 | ||||
|   const time_zone tz = utc_time_zone(); | ||||
|   const auto tp = system_clock::from_time_t(536457599); | ||||
|   const auto tp = chrono::system_clock::from_time_t(536457599); | ||||
|   ExpectTime(tp, tz, 1986, 12, 31, 23, 59, 59, 0, false, "UTC"); | ||||
|   EXPECT_EQ(weekday::wednesday, get_weekday(civil_day(convert(tp, tz)))); | ||||
| } | ||||
| 
 | ||||
| TEST(TimeZoneImpl, LocalTimeInFixed) { | ||||
|   const sys_seconds offset = -(hours(8) + minutes(33) + seconds(47)); | ||||
|   const absl::time_internal::cctz::seconds offset = | ||||
|       -(chrono::hours(8) + chrono::minutes(33) + chrono::seconds(47)); | ||||
|   const time_zone tz = fixed_time_zone(offset); | ||||
|   const auto tp = system_clock::from_time_t(0); | ||||
|   const auto tp = chrono::system_clock::from_time_t(0); | ||||
|   ExpectTime(tp, tz, 1969, 12, 31, 15, 26, 13, offset.count(), false, | ||||
|              "-083347"); | ||||
|   EXPECT_EQ(weekday::wednesday, get_weekday(civil_day(convert(tp, tz)))); | ||||
|  | @ -852,52 +851,52 @@ TEST(TimeZoneImpl, LocalTimeInFixed) { | |||
| 
 | ||||
| TEST(BreakTime, LocalTimeInNewYork) { | ||||
|   const time_zone tz = LoadZone("America/New_York"); | ||||
|   const auto tp = system_clock::from_time_t(45); | ||||
|   const auto tp = chrono::system_clock::from_time_t(45); | ||||
|   ExpectTime(tp, tz, 1969, 12, 31, 19, 0, 45, -5 * 60 * 60, false, "EST"); | ||||
|   EXPECT_EQ(weekday::wednesday, get_weekday(civil_day(convert(tp, tz)))); | ||||
| } | ||||
| 
 | ||||
| TEST(BreakTime, LocalTimeInMTV) { | ||||
|   const time_zone tz = LoadZone("America/Los_Angeles"); | ||||
|   const auto tp = system_clock::from_time_t(1380855729); | ||||
|   const auto tp = chrono::system_clock::from_time_t(1380855729); | ||||
|   ExpectTime(tp, tz, 2013, 10, 3, 20, 2, 9, -7 * 60 * 60, true, "PDT"); | ||||
|   EXPECT_EQ(weekday::thursday, get_weekday(civil_day(convert(tp, tz)))); | ||||
| } | ||||
| 
 | ||||
| TEST(BreakTime, LocalTimeInSydney) { | ||||
|   const time_zone tz = LoadZone("Australia/Sydney"); | ||||
|   const auto tp = system_clock::from_time_t(90); | ||||
|   const auto tp = chrono::system_clock::from_time_t(90); | ||||
|   ExpectTime(tp, tz, 1970, 1, 1, 10, 1, 30, 10 * 60 * 60, false, "AEST"); | ||||
|   EXPECT_EQ(weekday::thursday, get_weekday(civil_day(convert(tp, tz)))); | ||||
| } | ||||
| 
 | ||||
| TEST(MakeTime, TimePointResolution) { | ||||
|   const time_zone utc = utc_time_zone(); | ||||
|   const time_point<nanoseconds> tp_ns = | ||||
|   const time_point<chrono::nanoseconds> tp_ns = | ||||
|       convert(civil_second(2015, 1, 2, 3, 4, 5), utc); | ||||
|   EXPECT_EQ("04:05", format("%M:%E*S", tp_ns, utc)); | ||||
|   const time_point<microseconds> tp_us = | ||||
|   const time_point<chrono::microseconds> tp_us = | ||||
|       convert(civil_second(2015, 1, 2, 3, 4, 5), utc); | ||||
|   EXPECT_EQ("04:05", format("%M:%E*S", tp_us, utc)); | ||||
|   const time_point<milliseconds> tp_ms = | ||||
|   const time_point<chrono::milliseconds> tp_ms = | ||||
|       convert(civil_second(2015, 1, 2, 3, 4, 5), utc); | ||||
|   EXPECT_EQ("04:05", format("%M:%E*S", tp_ms, utc)); | ||||
|   const time_point<seconds> tp_s = | ||||
|   const time_point<chrono::seconds> tp_s = | ||||
|       convert(civil_second(2015, 1, 2, 3, 4, 5), utc); | ||||
|   EXPECT_EQ("04:05", format("%M:%E*S", tp_s, utc)); | ||||
|   const time_point<sys_seconds> tp_s64 = | ||||
|   const time_point<absl::time_internal::cctz::seconds> tp_s64 = | ||||
|       convert(civil_second(2015, 1, 2, 3, 4, 5), utc); | ||||
|   EXPECT_EQ("04:05", format("%M:%E*S", tp_s64, utc)); | ||||
| 
 | ||||
|   // These next two require time_point_cast because the conversion from a
 | ||||
|   // resolution of seconds (the return value of convert()) to a coarser
 | ||||
|   // resolution requires an explicit cast.
 | ||||
|   const time_point<minutes> tp_m = | ||||
|       time_point_cast<minutes>( | ||||
|   // These next two require chrono::time_point_cast because the conversion
 | ||||
|   // from a resolution of seconds (the return value of convert()) to a
 | ||||
|   // coarser resolution requires an explicit cast.
 | ||||
|   const time_point<chrono::minutes> tp_m = | ||||
|       chrono::time_point_cast<chrono::minutes>( | ||||
|           convert(civil_second(2015, 1, 2, 3, 4, 5), utc)); | ||||
|   EXPECT_EQ("04:00", format("%M:%E*S", tp_m, utc)); | ||||
|   const time_point<hours> tp_h = | ||||
|       time_point_cast<hours>( | ||||
|   const time_point<chrono::hours> tp_h = | ||||
|       chrono::time_point_cast<chrono::hours>( | ||||
|           convert(civil_second(2015, 1, 2, 3, 4, 5), utc)); | ||||
|   EXPECT_EQ("00:00", format("%M:%E*S", tp_h, utc)); | ||||
| } | ||||
|  | @ -905,7 +904,7 @@ TEST(MakeTime, TimePointResolution) { | |||
| TEST(MakeTime, Normalization) { | ||||
|   const time_zone tz = LoadZone("America/New_York"); | ||||
|   const auto tp = convert(civil_second(2009, 2, 13, 18, 31, 30), tz); | ||||
|   EXPECT_EQ(system_clock::from_time_t(1234567890), tp); | ||||
|   EXPECT_EQ(chrono::system_clock::from_time_t(1234567890), tp); | ||||
| 
 | ||||
|   // Now requests for the same time_point but with out-of-range fields.
 | ||||
|   EXPECT_EQ(tp, convert(civil_second(2008, 14, 13, 18, 31, 30), tz));  // month
 | ||||
|  | @ -919,67 +918,67 @@ TEST(MakeTime, Normalization) { | |||
| TEST(MakeTime, SysSecondsLimits) { | ||||
|   const char RFC3339[] =  "%Y-%m-%dT%H:%M:%S%Ez"; | ||||
|   const time_zone utc = utc_time_zone(); | ||||
|   const time_zone east = fixed_time_zone(hours(14)); | ||||
|   const time_zone west = fixed_time_zone(-hours(14)); | ||||
|   time_point<sys_seconds> tp; | ||||
|   const time_zone east = fixed_time_zone(chrono::hours(14)); | ||||
|   const time_zone west = fixed_time_zone(-chrono::hours(14)); | ||||
|   time_point<absl::time_internal::cctz::seconds> tp; | ||||
| 
 | ||||
|   // Approach the maximal time_point<sys_seconds> value from below.
 | ||||
|   // Approach the maximal time_point<cctz::seconds> value from below.
 | ||||
|   tp = convert(civil_second(292277026596, 12, 4, 15, 30, 6), utc); | ||||
|   EXPECT_EQ("292277026596-12-04T15:30:06+00:00", format(RFC3339, tp, utc)); | ||||
|   tp = convert(civil_second(292277026596, 12, 4, 15, 30, 7), utc); | ||||
|   EXPECT_EQ("292277026596-12-04T15:30:07+00:00", format(RFC3339, tp, utc)); | ||||
|   EXPECT_EQ(time_point<sys_seconds>::max(), tp); | ||||
|   EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::max(), tp); | ||||
|   tp = convert(civil_second(292277026596, 12, 4, 15, 30, 8), utc); | ||||
|   EXPECT_EQ(time_point<sys_seconds>::max(), tp); | ||||
|   EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::max(), tp); | ||||
|   tp = convert(civil_second::max(), utc); | ||||
|   EXPECT_EQ(time_point<sys_seconds>::max(), tp); | ||||
|   EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::max(), tp); | ||||
| 
 | ||||
|   // Checks that we can also get the maximal value for a far-east zone.
 | ||||
|   tp = convert(civil_second(292277026596, 12, 5, 5, 30, 7), east); | ||||
|   EXPECT_EQ("292277026596-12-05T05:30:07+14:00", format(RFC3339, tp, east)); | ||||
|   EXPECT_EQ(time_point<sys_seconds>::max(), tp); | ||||
|   EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::max(), tp); | ||||
|   tp = convert(civil_second(292277026596, 12, 5, 5, 30, 8), east); | ||||
|   EXPECT_EQ(time_point<sys_seconds>::max(), tp); | ||||
|   EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::max(), tp); | ||||
|   tp = convert(civil_second::max(), east); | ||||
|   EXPECT_EQ(time_point<sys_seconds>::max(), tp); | ||||
|   EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::max(), tp); | ||||
| 
 | ||||
|   // Checks that we can also get the maximal value for a far-west zone.
 | ||||
|   tp = convert(civil_second(292277026596, 12, 4, 1, 30, 7), west); | ||||
|   EXPECT_EQ("292277026596-12-04T01:30:07-14:00", format(RFC3339, tp, west)); | ||||
|   EXPECT_EQ(time_point<sys_seconds>::max(), tp); | ||||
|   EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::max(), tp); | ||||
|   tp = convert(civil_second(292277026596, 12, 4, 7, 30, 8), west); | ||||
|   EXPECT_EQ(time_point<sys_seconds>::max(), tp); | ||||
|   EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::max(), tp); | ||||
|   tp = convert(civil_second::max(), west); | ||||
|   EXPECT_EQ(time_point<sys_seconds>::max(), tp); | ||||
|   EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::max(), tp); | ||||
| 
 | ||||
|   // Approach the minimal time_point<sys_seconds> value from above.
 | ||||
|   // Approach the minimal time_point<cctz::seconds> value from above.
 | ||||
|   tp = convert(civil_second(-292277022657, 1, 27, 8, 29, 53), utc); | ||||
|   EXPECT_EQ("-292277022657-01-27T08:29:53+00:00", format(RFC3339, tp, utc)); | ||||
|   tp = convert(civil_second(-292277022657, 1, 27, 8, 29, 52), utc); | ||||
|   EXPECT_EQ("-292277022657-01-27T08:29:52+00:00", format(RFC3339, tp, utc)); | ||||
|   EXPECT_EQ(time_point<sys_seconds>::min(), tp); | ||||
|   EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::min(), tp); | ||||
|   tp = convert(civil_second(-292277022657, 1, 27, 8, 29, 51), utc); | ||||
|   EXPECT_EQ(time_point<sys_seconds>::min(), tp); | ||||
|   EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::min(), tp); | ||||
|   tp = convert(civil_second::min(), utc); | ||||
|   EXPECT_EQ(time_point<sys_seconds>::min(), tp); | ||||
|   EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::min(), tp); | ||||
| 
 | ||||
|   // Checks that we can also get the minimal value for a far-east zone.
 | ||||
|   tp = convert(civil_second(-292277022657, 1, 27, 22, 29, 52), east); | ||||
|   EXPECT_EQ("-292277022657-01-27T22:29:52+14:00", format(RFC3339, tp, east)); | ||||
|   EXPECT_EQ(time_point<sys_seconds>::min(), tp); | ||||
|   EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::min(), tp); | ||||
|   tp = convert(civil_second(-292277022657, 1, 27, 22, 29, 51), east); | ||||
|   EXPECT_EQ(time_point<sys_seconds>::min(), tp); | ||||
|   EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::min(), tp); | ||||
|   tp = convert(civil_second::min(), east); | ||||
|   EXPECT_EQ(time_point<sys_seconds>::min(), tp); | ||||
|   EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::min(), tp); | ||||
| 
 | ||||
|   // Checks that we can also get the minimal value for a far-west zone.
 | ||||
|   tp = convert(civil_second(-292277022657, 1, 26, 18, 29, 52), west); | ||||
|   EXPECT_EQ("-292277022657-01-26T18:29:52-14:00", format(RFC3339, tp, west)); | ||||
|   EXPECT_EQ(time_point<sys_seconds>::min(), tp); | ||||
|   EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::min(), tp); | ||||
|   tp = convert(civil_second(-292277022657, 1, 26, 18, 29, 51), west); | ||||
|   EXPECT_EQ(time_point<sys_seconds>::min(), tp); | ||||
|   EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::min(), tp); | ||||
|   tp = convert(civil_second::min(), west); | ||||
|   EXPECT_EQ(time_point<sys_seconds>::min(), tp); | ||||
|   EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::min(), tp); | ||||
| } | ||||
| 
 | ||||
| TEST(TimeZoneEdgeCase, AmericaNewYork) { | ||||
|  | @ -988,13 +987,13 @@ TEST(TimeZoneEdgeCase, AmericaNewYork) { | |||
|   // Spring 1:59:59 -> 3:00:00
 | ||||
|   auto tp = convert(civil_second(2013, 3, 10, 1, 59, 59), tz); | ||||
|   ExpectTime(tp, tz, 2013, 3, 10, 1, 59, 59, -5 * 3600, false, "EST"); | ||||
|   tp += seconds(1); | ||||
|   tp += absl::time_internal::cctz::seconds(1); | ||||
|   ExpectTime(tp, tz, 2013, 3, 10, 3, 0, 0, -4 * 3600, true, "EDT"); | ||||
| 
 | ||||
|   // Fall 1:59:59 -> 1:00:00
 | ||||
|   tp = convert(civil_second(2013, 11, 3, 1, 59, 59), tz); | ||||
|   ExpectTime(tp, tz, 2013, 11, 3, 1, 59, 59, -4 * 3600, true, "EDT"); | ||||
|   tp += seconds(1); | ||||
|   tp += absl::time_internal::cctz::seconds(1); | ||||
|   ExpectTime(tp, tz, 2013, 11, 3, 1, 0, 0, -5 * 3600, false, "EST"); | ||||
| } | ||||
| 
 | ||||
|  | @ -1004,13 +1003,13 @@ TEST(TimeZoneEdgeCase, AmericaLosAngeles) { | |||
|   // Spring 1:59:59 -> 3:00:00
 | ||||
|   auto tp = convert(civil_second(2013, 3, 10, 1, 59, 59), tz); | ||||
|   ExpectTime(tp, tz, 2013, 3, 10, 1, 59, 59, -8 * 3600, false, "PST"); | ||||
|   tp += seconds(1); | ||||
|   tp += absl::time_internal::cctz::seconds(1); | ||||
|   ExpectTime(tp, tz, 2013, 3, 10, 3, 0, 0, -7 * 3600, true, "PDT"); | ||||
| 
 | ||||
|   // Fall 1:59:59 -> 1:00:00
 | ||||
|   tp = convert(civil_second(2013, 11, 3, 1, 59, 59), tz); | ||||
|   ExpectTime(tp, tz, 2013, 11, 3, 1, 59, 59, -7 * 3600, true, "PDT"); | ||||
|   tp += seconds(1); | ||||
|   tp += absl::time_internal::cctz::seconds(1); | ||||
|   ExpectTime(tp, tz, 2013, 11, 3, 1, 0, 0, -8 * 3600, false, "PST"); | ||||
| } | ||||
| 
 | ||||
|  | @ -1020,13 +1019,13 @@ TEST(TimeZoneEdgeCase, ArizonaNoTransition) { | |||
|   // No transition in Spring.
 | ||||
|   auto tp = convert(civil_second(2013, 3, 10, 1, 59, 59), tz); | ||||
|   ExpectTime(tp, tz, 2013, 3, 10, 1, 59, 59, -7 * 3600, false, "MST"); | ||||
|   tp += seconds(1); | ||||
|   tp += absl::time_internal::cctz::seconds(1); | ||||
|   ExpectTime(tp, tz, 2013, 3, 10, 2, 0, 0, -7 * 3600, false, "MST"); | ||||
| 
 | ||||
|   // No transition in Fall.
 | ||||
|   tp = convert(civil_second(2013, 11, 3, 1, 59, 59), tz); | ||||
|   ExpectTime(tp, tz, 2013, 11, 3, 1, 59, 59, -7 * 3600, false, "MST"); | ||||
|   tp += seconds(1); | ||||
|   tp += absl::time_internal::cctz::seconds(1); | ||||
|   ExpectTime(tp, tz, 2013, 11, 3, 2, 0, 0, -7 * 3600, false, "MST"); | ||||
| } | ||||
| 
 | ||||
|  | @ -1039,7 +1038,7 @@ TEST(TimeZoneEdgeCase, AsiaKathmandu) { | |||
|   //   504901800 == Wed,  1 Jan 1986 00:15:00 +0545 (+0545)
 | ||||
|   auto tp = convert(civil_second(1985, 12, 31, 23, 59, 59), tz); | ||||
|   ExpectTime(tp, tz, 1985, 12, 31, 23, 59, 59, 5.5 * 3600, false, "+0530"); | ||||
|   tp += seconds(1); | ||||
|   tp += absl::time_internal::cctz::seconds(1); | ||||
|   ExpectTime(tp, tz, 1986, 1, 1, 0, 15, 0, 5.75 * 3600, false, "+0545"); | ||||
| } | ||||
| 
 | ||||
|  | @ -1052,14 +1051,14 @@ TEST(TimeZoneEdgeCase, PacificChatham) { | |||
|   //   1365256800 == Sun,  7 Apr 2013 02:45:00 +1245 (+1245)
 | ||||
|   auto tp = convert(civil_second(2013, 4, 7, 3, 44, 59), tz); | ||||
|   ExpectTime(tp, tz, 2013, 4, 7, 3, 44, 59, 13.75 * 3600, true, "+1345"); | ||||
|   tp += seconds(1); | ||||
|   tp += absl::time_internal::cctz::seconds(1); | ||||
|   ExpectTime(tp, tz, 2013, 4, 7, 2, 45, 0, 12.75 * 3600, false, "+1245"); | ||||
| 
 | ||||
|   //   1380376799 == Sun, 29 Sep 2013 02:44:59 +1245 (+1245)
 | ||||
|   //   1380376800 == Sun, 29 Sep 2013 03:45:00 +1345 (+1345)
 | ||||
|   tp = convert(civil_second(2013, 9, 29, 2, 44, 59), tz); | ||||
|   ExpectTime(tp, tz, 2013, 9, 29, 2, 44, 59, 12.75 * 3600, false, "+1245"); | ||||
|   tp += seconds(1); | ||||
|   tp += absl::time_internal::cctz::seconds(1); | ||||
|   ExpectTime(tp, tz, 2013, 9, 29, 3, 45, 0, 13.75 * 3600, true, "+1345"); | ||||
| } | ||||
| 
 | ||||
|  | @ -1072,14 +1071,14 @@ TEST(TimeZoneEdgeCase, AustraliaLordHowe) { | |||
|   //   1365260400 == Sun,  7 Apr 2013 01:30:00 +1030 (+1030)
 | ||||
|   auto tp = convert(civil_second(2013, 4, 7, 1, 59, 59), tz); | ||||
|   ExpectTime(tp, tz, 2013, 4, 7, 1, 59, 59, 11 * 3600, true, "+11"); | ||||
|   tp += seconds(1); | ||||
|   tp += absl::time_internal::cctz::seconds(1); | ||||
|   ExpectTime(tp, tz, 2013, 4, 7, 1, 30, 0, 10.5 * 3600, false, "+1030"); | ||||
| 
 | ||||
|   //   1380986999 == Sun,  6 Oct 2013 01:59:59 +1030 (+1030)
 | ||||
|   //   1380987000 == Sun,  6 Oct 2013 02:30:00 +1100 (+11)
 | ||||
|   tp = convert(civil_second(2013, 10, 6, 1, 59, 59), tz); | ||||
|   ExpectTime(tp, tz, 2013, 10, 6, 1, 59, 59, 10.5 * 3600, false, "+1030"); | ||||
|   tp += seconds(1); | ||||
|   tp += absl::time_internal::cctz::seconds(1); | ||||
|   ExpectTime(tp, tz, 2013, 10, 6, 2, 30, 0, 11 * 3600, true, "+11"); | ||||
| } | ||||
| 
 | ||||
|  | @ -1097,7 +1096,7 @@ TEST(TimeZoneEdgeCase, PacificApia) { | |||
|   auto tp = convert(civil_second(2011, 12, 29, 23, 59, 59), tz); | ||||
|   ExpectTime(tp, tz, 2011, 12, 29, 23, 59, 59, -10 * 3600, true, "-10"); | ||||
|   EXPECT_EQ(363, get_yearday(civil_day(convert(tp, tz)))); | ||||
|   tp += seconds(1); | ||||
|   tp += absl::time_internal::cctz::seconds(1); | ||||
|   ExpectTime(tp, tz, 2011, 12, 31, 0, 0, 0, 14 * 3600, true, "+14"); | ||||
|   EXPECT_EQ(365, get_yearday(civil_day(convert(tp, tz)))); | ||||
| } | ||||
|  | @ -1114,7 +1113,7 @@ TEST(TimeZoneEdgeCase, AfricaCairo) { | |||
|   //   1400191200 == Fri, 16 May 2014 01:00:00 +0300 (EEST)
 | ||||
|   auto tp = convert(civil_second(2014, 5, 15, 23, 59, 59), tz); | ||||
|   ExpectTime(tp, tz, 2014, 5, 15, 23, 59, 59, 2 * 3600, false, "EET"); | ||||
|   tp += seconds(1); | ||||
|   tp += absl::time_internal::cctz::seconds(1); | ||||
|   ExpectTime(tp, tz, 2014, 5, 16, 1, 0, 0, 3 * 3600, true, "EEST"); | ||||
| #endif | ||||
| } | ||||
|  | @ -1131,7 +1130,7 @@ TEST(TimeZoneEdgeCase, AfricaMonrovia) { | |||
|   //   63593070 == Fri,  7 Jan 1972 00:44:30 +0000 (GMT)
 | ||||
|   auto tp = convert(civil_second(1972, 1, 6, 23, 59, 59), tz); | ||||
|   ExpectTime(tp, tz, 1972, 1, 6, 23, 59, 59, -44.5 * 60, false, "MMT"); | ||||
|   tp += seconds(1); | ||||
|   tp += absl::time_internal::cctz::seconds(1); | ||||
|   ExpectTime(tp, tz, 1972, 1, 7, 0, 44, 30, 0 * 60, false, "GMT"); | ||||
| #endif | ||||
| } | ||||
|  | @ -1159,7 +1158,7 @@ TEST(TimeZoneEdgeCase, AmericaJamaica) { | |||
|   tp = convert(civil_second(1889, 12, 31, 23, 59, 59), tz); | ||||
|   ExpectTime(tp, tz, 1889, 12, 31, 23, 59, 59, -18430, false, | ||||
|              tz.lookup(tp).abbr); | ||||
|   tp += seconds(1); | ||||
|   tp += absl::time_internal::cctz::seconds(1); | ||||
|   ExpectTime(tp, tz, 1890, 1, 1, 0, 0, 0, -18430, false, "KMT"); | ||||
| #endif | ||||
| 
 | ||||
|  | @ -1168,7 +1167,7 @@ TEST(TimeZoneEdgeCase, AmericaJamaica) { | |||
|   //     436341600 == Sun, 30 Oct 1983 01:00:00 -0500 (EST)
 | ||||
|   tp = convert(civil_second(1983, 10, 30, 1, 59, 59), tz); | ||||
|   ExpectTime(tp, tz, 1983, 10, 30, 1, 59, 59, -4 * 3600, true, "EDT"); | ||||
|   tp += seconds(1); | ||||
|   tp += absl::time_internal::cctz::seconds(1); | ||||
|   ExpectTime(tp, tz, 1983, 10, 30, 1, 0, 0, -5 * 3600, false, "EST"); | ||||
| 
 | ||||
|   // After the last transition.
 | ||||
|  | @ -1189,7 +1188,7 @@ TEST(TimeZoneEdgeCase, WET) { | |||
|   //     228877200 == Sun,  3 Apr 1977 02:00:00 +0100 (WEST)
 | ||||
|   tp = convert(civil_second(1977, 4, 3, 0, 59, 59), tz); | ||||
|   ExpectTime(tp, tz, 1977, 4, 3, 0, 59, 59, 0, false, "WET"); | ||||
|   tp += seconds(1); | ||||
|   tp += absl::time_internal::cctz::seconds(1); | ||||
|   ExpectTime(tp, tz, 1977, 4, 3, 2, 0, 0, 1 * 3600, true, "WEST"); | ||||
| 
 | ||||
|   // A non-existent time within the first transition.
 | ||||
|  | @ -1211,12 +1210,12 @@ TEST(TimeZoneEdgeCase, FixedOffsets) { | |||
|   const time_zone gmtm5 = LoadZone("Etc/GMT+5");  // -0500
 | ||||
|   auto tp = convert(civil_second(1970, 1, 1, 0, 0, 0), gmtm5); | ||||
|   ExpectTime(tp, gmtm5, 1970, 1, 1, 0, 0, 0, -5 * 3600, false, "-05"); | ||||
|   EXPECT_EQ(system_clock::from_time_t(5 * 3600), tp); | ||||
|   EXPECT_EQ(chrono::system_clock::from_time_t(5 * 3600), tp); | ||||
| 
 | ||||
|   const time_zone gmtp5 = LoadZone("Etc/GMT-5");  // +0500
 | ||||
|   tp = convert(civil_second(1970, 1, 1, 0, 0, 0), gmtp5); | ||||
|   ExpectTime(tp, gmtp5, 1970, 1, 1, 0, 0, 0, 5 * 3600, false, "+05"); | ||||
|   EXPECT_EQ(system_clock::from_time_t(-5 * 3600), tp); | ||||
|   EXPECT_EQ(chrono::system_clock::from_time_t(-5 * 3600), tp); | ||||
| } | ||||
| 
 | ||||
| TEST(TimeZoneEdgeCase, NegativeYear) { | ||||
|  | @ -1225,7 +1224,7 @@ TEST(TimeZoneEdgeCase, NegativeYear) { | |||
|   auto tp = convert(civil_second(0, 1, 1, 0, 0, 0), tz); | ||||
|   ExpectTime(tp, tz, 0, 1, 1, 0, 0, 0, 0 * 3600, false, "UTC"); | ||||
|   EXPECT_EQ(weekday::saturday, get_weekday(civil_day(convert(tp, tz)))); | ||||
|   tp -= seconds(1); | ||||
|   tp -= absl::time_internal::cctz::seconds(1); | ||||
|   ExpectTime(tp, tz, -1, 12, 31, 23, 59, 59, 0 * 3600, false, "UTC"); | ||||
|   EXPECT_EQ(weekday::friday, get_weekday(civil_day(convert(tp, tz)))); | ||||
| } | ||||
|  | @ -1239,7 +1238,7 @@ TEST(TimeZoneEdgeCase, UTC32bitLimit) { | |||
|   //   2147483648 == Tue, 19 Jan 2038 03:14:08 +0000 (UTC)
 | ||||
|   auto tp = convert(civil_second(2038, 1, 19, 3, 14, 7), tz); | ||||
|   ExpectTime(tp, tz, 2038, 1, 19, 3, 14, 7, 0 * 3600, false, "UTC"); | ||||
|   tp += seconds(1); | ||||
|   tp += absl::time_internal::cctz::seconds(1); | ||||
|   ExpectTime(tp, tz, 2038, 1, 19, 3, 14, 8, 0 * 3600, false, "UTC"); | ||||
| } | ||||
| 
 | ||||
|  | @ -1252,7 +1251,7 @@ TEST(TimeZoneEdgeCase, UTC5DigitYear) { | |||
|   //   253402300800 == Sat,  1 Jan 1000 00:00:00 +0000 (UTC)
 | ||||
|   auto tp = convert(civil_second(9999, 12, 31, 23, 59, 59), tz); | ||||
|   ExpectTime(tp, tz, 9999, 12, 31, 23, 59, 59, 0 * 3600, false, "UTC"); | ||||
|   tp += seconds(1); | ||||
|   tp += absl::time_internal::cctz::seconds(1); | ||||
|   ExpectTime(tp, tz, 10000, 1, 1, 0, 0, 0, 0 * 3600, false, "UTC"); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -60,9 +60,17 @@ ZoneInfoSourceFactory default_factory = DefaultFactory; | |||
| #else | ||||
| #error Unsupported MSVC platform | ||||
| #endif | ||||
| #else | ||||
| #else  // _MSC_VER
 | ||||
| #if !defined(__has_attribute) | ||||
| #define __has_attribute(x) 0 | ||||
| #endif | ||||
| #if __has_attribute(weak) || defined(__GNUC__) | ||||
| ZoneInfoSourceFactory zone_info_source_factory | ||||
|     __attribute__((weak)) = DefaultFactory; | ||||
| #else | ||||
| // Make it a "strong" definition if we have no other choice.
 | ||||
| ZoneInfoSourceFactory zone_info_source_factory = DefaultFactory; | ||||
| #endif | ||||
| #endif  // _MSC_VER
 | ||||
| 
 | ||||
| }  // namespace cctz_extension
 | ||||
|  |  | |||
|  | @ -44,8 +44,8 @@ namespace absl { | |||
| 
 | ||||
| namespace { | ||||
| 
 | ||||
| inline cctz::time_point<cctz::sys_seconds> unix_epoch() { | ||||
|   return std::chrono::time_point_cast<cctz::sys_seconds>( | ||||
| inline cctz::time_point<cctz::seconds> unix_epoch() { | ||||
|   return std::chrono::time_point_cast<cctz::seconds>( | ||||
|       std::chrono::system_clock::from_time_t(0)); | ||||
| } | ||||
| 
 | ||||
|  | @ -110,12 +110,12 @@ inline TimeConversion InfinitePastTimeConversion() { | |||
| 
 | ||||
| // Makes a Time from sec, overflowing to InfiniteFuture/InfinitePast as
 | ||||
| // necessary. If sec is min/max, then consult cs+tz to check for overlow.
 | ||||
| Time MakeTimeWithOverflow(const cctz::time_point<cctz::sys_seconds>& sec, | ||||
| Time MakeTimeWithOverflow(const cctz::time_point<cctz::seconds>& sec, | ||||
|                           const cctz::civil_second& cs, | ||||
|                           const cctz::time_zone& tz, | ||||
|                           bool* normalized = nullptr) { | ||||
|   const auto max = cctz::time_point<cctz::sys_seconds>::max(); | ||||
|   const auto min = cctz::time_point<cctz::sys_seconds>::min(); | ||||
|   const auto max = cctz::time_point<cctz::seconds>::max(); | ||||
|   const auto min = cctz::time_point<cctz::seconds>::min(); | ||||
|   if (sec == max) { | ||||
|     const auto al = tz.lookup(max); | ||||
|     if (cs > al.cs) { | ||||
|  | @ -174,8 +174,7 @@ absl::Time::Breakdown Time::In(absl::TimeZone tz) const { | |||
|   if (*this == absl::InfiniteFuture()) return absl::InfiniteFutureBreakdown(); | ||||
|   if (*this == absl::InfinitePast()) return absl::InfinitePastBreakdown(); | ||||
| 
 | ||||
|   const auto tp = | ||||
|       unix_epoch() + cctz::sys_seconds(time_internal::GetRepHi(rep_)); | ||||
|   const auto tp = unix_epoch() + cctz::seconds(time_internal::GetRepHi(rep_)); | ||||
|   const auto al = cctz::time_zone(tz).lookup(tp); | ||||
|   const auto cs = al.cs; | ||||
|   const auto cd = cctz::civil_day(cs); | ||||
|  |  | |||
|  | @ -59,7 +59,7 @@ TEST(TimeZone, DefaultTimeZones) { | |||
| 
 | ||||
| TEST(TimeZone, FixedTimeZone) { | ||||
|   const absl::TimeZone tz = absl::FixedTimeZone(123); | ||||
|   const cctz::time_zone cz = cctz::fixed_time_zone(cctz::sys_seconds(123)); | ||||
|   const cctz::time_zone cz = cctz::fixed_time_zone(cctz::seconds(123)); | ||||
|   EXPECT_EQ(tz, absl::TimeZone(cz)); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -579,12 +579,9 @@ struct VariantCoreAccess { | |||
|     self.index_ = other.index(); | ||||
|   } | ||||
| 
 | ||||
|   // Access a variant alternative, assuming the index is correct.
 | ||||
|   template <std::size_t I, class Variant> | ||||
|   static VariantAccessResult<I, Variant> Access(Variant&& self) { | ||||
|     if (ABSL_PREDICT_FALSE(self.index_ != I)) { | ||||
|       TypedThrowBadVariantAccess<VariantAccessResult<I, Variant>>(); | ||||
|     } | ||||
| 
 | ||||
|     // This cast instead of invocation of AccessUnion with an rvalue is a
 | ||||
|     // workaround for msvc. Without this there is a runtime failure when dealing
 | ||||
|     // with rvalues.
 | ||||
|  | @ -593,6 +590,16 @@ struct VariantCoreAccess { | |||
|         variant_internal::AccessUnion(self.state_, SizeT<I>())); | ||||
|   } | ||||
| 
 | ||||
|   // Access a variant alternative, throwing if the index is incorrect.
 | ||||
|   template <std::size_t I, class Variant> | ||||
|   static VariantAccessResult<I, Variant> CheckedAccess(Variant&& self) { | ||||
|     if (ABSL_PREDICT_FALSE(self.index_ != I)) { | ||||
|       TypedThrowBadVariantAccess<VariantAccessResult<I, Variant>>(); | ||||
|     } | ||||
| 
 | ||||
|     return Access<I>(absl::forward<Variant>(self)); | ||||
|   } | ||||
| 
 | ||||
|   // The implementation of the move-assignment operation for a variant.
 | ||||
|   template <class VType> | ||||
|   struct MoveAssignVisitor { | ||||
|  |  | |||
|  | @ -290,7 +290,7 @@ constexpr bool holds_alternative(const variant<Types...>& v) noexcept { | |||
| // Overload for getting a variant's lvalue by type.
 | ||||
| template <class T, class... Types> | ||||
| constexpr T& get(variant<Types...>& v) {  // NOLINT
 | ||||
|   return variant_internal::VariantCoreAccess::Access< | ||||
|   return variant_internal::VariantCoreAccess::CheckedAccess< | ||||
|       variant_internal::IndexOf<T, Types...>::value>(v); | ||||
| } | ||||
| 
 | ||||
|  | @ -298,14 +298,14 @@ constexpr T& get(variant<Types...>& v) {  // NOLINT | |||
| // Note: `absl::move()` is required to allow use of constexpr in C++11.
 | ||||
| template <class T, class... Types> | ||||
| constexpr T&& get(variant<Types...>&& v) { | ||||
|   return variant_internal::VariantCoreAccess::Access< | ||||
|   return variant_internal::VariantCoreAccess::CheckedAccess< | ||||
|       variant_internal::IndexOf<T, Types...>::value>(absl::move(v)); | ||||
| } | ||||
| 
 | ||||
| // Overload for getting a variant's const lvalue by type.
 | ||||
| template <class T, class... Types> | ||||
| constexpr const T& get(const variant<Types...>& v) { | ||||
|   return variant_internal::VariantCoreAccess::Access< | ||||
|   return variant_internal::VariantCoreAccess::CheckedAccess< | ||||
|       variant_internal::IndexOf<T, Types...>::value>(v); | ||||
| } | ||||
| 
 | ||||
|  | @ -313,7 +313,7 @@ constexpr const T& get(const variant<Types...>& v) { | |||
| // Note: `absl::move()` is required to allow use of constexpr in C++11.
 | ||||
| template <class T, class... Types> | ||||
| constexpr const T&& get(const variant<Types...>&& v) { | ||||
|   return variant_internal::VariantCoreAccess::Access< | ||||
|   return variant_internal::VariantCoreAccess::CheckedAccess< | ||||
|       variant_internal::IndexOf<T, Types...>::value>(absl::move(v)); | ||||
| } | ||||
| 
 | ||||
|  | @ -321,7 +321,7 @@ constexpr const T&& get(const variant<Types...>&& v) { | |||
| template <std::size_t I, class... Types> | ||||
| constexpr variant_alternative_t<I, variant<Types...>>& get( | ||||
|     variant<Types...>& v) {  // NOLINT
 | ||||
|   return variant_internal::VariantCoreAccess::Access<I>(v); | ||||
|   return variant_internal::VariantCoreAccess::CheckedAccess<I>(v); | ||||
| } | ||||
| 
 | ||||
| // Overload for getting a variant's rvalue by index.
 | ||||
|  | @ -329,14 +329,14 @@ constexpr variant_alternative_t<I, variant<Types...>>& get( | |||
| template <std::size_t I, class... Types> | ||||
| constexpr variant_alternative_t<I, variant<Types...>>&& get( | ||||
|     variant<Types...>&& v) { | ||||
|   return variant_internal::VariantCoreAccess::Access<I>(absl::move(v)); | ||||
|   return variant_internal::VariantCoreAccess::CheckedAccess<I>(absl::move(v)); | ||||
| } | ||||
| 
 | ||||
| // Overload for getting a variant's const lvalue by index.
 | ||||
| template <std::size_t I, class... Types> | ||||
| constexpr const variant_alternative_t<I, variant<Types...>>& get( | ||||
|     const variant<Types...>& v) { | ||||
|   return variant_internal::VariantCoreAccess::Access<I>(v); | ||||
|   return variant_internal::VariantCoreAccess::CheckedAccess<I>(v); | ||||
| } | ||||
| 
 | ||||
| // Overload for getting a variant's const rvalue by index.
 | ||||
|  | @ -344,7 +344,7 @@ constexpr const variant_alternative_t<I, variant<Types...>>& get( | |||
| template <std::size_t I, class... Types> | ||||
| constexpr const variant_alternative_t<I, variant<Types...>>&& get( | ||||
|     const variant<Types...>&& v) { | ||||
|   return variant_internal::VariantCoreAccess::Access<I>(absl::move(v)); | ||||
|   return variant_internal::VariantCoreAccess::CheckedAccess<I>(absl::move(v)); | ||||
| } | ||||
| 
 | ||||
| // get_if()
 | ||||
|  | @ -362,8 +362,10 @@ constexpr const variant_alternative_t<I, variant<Types...>>&& get( | |||
| template <std::size_t I, class... Types> | ||||
| constexpr absl::add_pointer_t<variant_alternative_t<I, variant<Types...>>> | ||||
| get_if(variant<Types...>* v) noexcept { | ||||
|   return (v != nullptr && v->index() == I) ? std::addressof(absl::get<I>(*v)) | ||||
|                                            : nullptr; | ||||
|   return (v != nullptr && v->index() == I) | ||||
|              ? std::addressof( | ||||
|                    variant_internal::VariantCoreAccess::Access<I>(*v)) | ||||
|              : nullptr; | ||||
| } | ||||
| 
 | ||||
| // Overload for getting a pointer to the const value stored in the given
 | ||||
|  | @ -371,8 +373,10 @@ get_if(variant<Types...>* v) noexcept { | |||
| template <std::size_t I, class... Types> | ||||
| constexpr absl::add_pointer_t<const variant_alternative_t<I, variant<Types...>>> | ||||
| get_if(const variant<Types...>* v) noexcept { | ||||
|   return (v != nullptr && v->index() == I) ? std::addressof(absl::get<I>(*v)) | ||||
|                                            : nullptr; | ||||
|   return (v != nullptr && v->index() == I) | ||||
|              ? std::addressof( | ||||
|                    variant_internal::VariantCoreAccess::Access<I>(*v)) | ||||
|              : nullptr; | ||||
| } | ||||
| 
 | ||||
| // Overload for getting a pointer to the value stored in the given variant by
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue