merge(3p/absl): subtree merge of Abseil up to e19260f
... notably, this includes Abseil's own StatusOr type, which conflicted with our implementation (that was taken from TensorFlow). Change-Id: Ie7d6764b64055caaeb8dc7b6b9d066291e6b538f
This commit is contained in:
		
							parent
							
								
									cc27324d02
								
							
						
					
					
						commit
						082c006c04
					
				
					 854 changed files with 11260 additions and 5296 deletions
				
			
		
							
								
								
									
										18
									
								
								third_party/abseil_cpp/absl/strings/BUILD.bazel
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										18
									
								
								third_party/abseil_cpp/absl/strings/BUILD.bazel
									
										
									
									
										vendored
									
									
								
							|  | @ -54,6 +54,7 @@ cc_library( | |||
|         "ascii.h", | ||||
|         "charconv.h", | ||||
|         "escaping.h", | ||||
|         "internal/string_constant.h", | ||||
|         "match.h", | ||||
|         "numbers.h", | ||||
|         "str_cat.h", | ||||
|  | @ -222,6 +223,19 @@ cc_test( | |||
|     ], | ||||
| ) | ||||
| 
 | ||||
| cc_test( | ||||
|     name = "string_constant_test", | ||||
|     size = "small", | ||||
|     srcs = ["internal/string_constant_test.cc"], | ||||
|     copts = ABSL_TEST_COPTS, | ||||
|     visibility = ["//visibility:private"], | ||||
|     deps = [ | ||||
|         ":strings", | ||||
|         "//absl/meta:type_traits", | ||||
|         "@com_google_googletest//:gtest_main", | ||||
|     ], | ||||
| ) | ||||
| 
 | ||||
| cc_test( | ||||
|     name = "string_view_benchmark", | ||||
|     srcs = ["string_view_benchmark.cc"], | ||||
|  | @ -258,6 +272,8 @@ cc_library( | |||
|     visibility = ["//visibility:private"], | ||||
|     deps = [ | ||||
|         ":strings", | ||||
|         "//absl/base:base_internal", | ||||
|         "//absl/container:compressed_tuple", | ||||
|         "//absl/meta:type_traits", | ||||
|     ], | ||||
| ) | ||||
|  | @ -277,7 +293,6 @@ cc_library( | |||
|         ":str_format", | ||||
|         ":strings", | ||||
|         "//absl/base", | ||||
|         "//absl/base:base_internal", | ||||
|         "//absl/base:core_headers", | ||||
|         "//absl/base:endian", | ||||
|         "//absl/base:raw_logging_internal", | ||||
|  | @ -720,6 +735,7 @@ cc_test( | |||
|     visibility = ["//visibility:private"], | ||||
|     deps = [ | ||||
|         ":str_format_internal", | ||||
|         ":strings", | ||||
|         "//absl/base:raw_logging_internal", | ||||
|         "//absl/types:optional", | ||||
|         "@com_google_googletest//:gtest_main", | ||||
|  |  | |||
|  | @ -21,6 +21,7 @@ absl_cc_library( | |||
|     "ascii.h" | ||||
|     "charconv.h" | ||||
|     "escaping.h" | ||||
|     "internal/string_constant.h" | ||||
|     "match.h" | ||||
|     "numbers.h" | ||||
|     "str_cat.h" | ||||
|  | @ -158,6 +159,19 @@ absl_cc_test( | |||
|     gmock_main | ||||
| ) | ||||
| 
 | ||||
| absl_cc_test( | ||||
|   NAME | ||||
|     string_constant_test | ||||
|   SRCS | ||||
|     "internal/string_constant_test.cc" | ||||
|   COPTS | ||||
|     ${ABSL_TEST_COPTS} | ||||
|   DEPS | ||||
|     absl::strings | ||||
|     absl::type_traits | ||||
|     gmock_main | ||||
| ) | ||||
| 
 | ||||
| absl_cc_test( | ||||
|   NAME | ||||
|     string_view_test | ||||
|  | @ -475,6 +489,7 @@ absl_cc_test( | |||
|   COPTS | ||||
|     ${ABSL_TEST_COPTS} | ||||
|   DEPS | ||||
|     absl::strings | ||||
|     absl::str_format_internal | ||||
|     absl::raw_logging_internal | ||||
|     absl::int128 | ||||
|  | @ -547,6 +562,7 @@ absl_cc_library( | |||
|   DEPS | ||||
|     absl::base | ||||
|     absl::base_internal | ||||
|     absl::compressed_tuple | ||||
|     absl::core_headers | ||||
|     absl::endian | ||||
|     absl::fixed_array | ||||
|  |  | |||
							
								
								
									
										168
									
								
								third_party/abseil_cpp/absl/strings/cord.cc
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										168
									
								
								third_party/abseil_cpp/absl/strings/cord.cc
									
										
									
									
										vendored
									
									
								
							|  | @ -50,58 +50,10 @@ using ::absl::cord_internal::CordRepConcat; | |||
| using ::absl::cord_internal::CordRepExternal; | ||||
| using ::absl::cord_internal::CordRepSubstring; | ||||
| 
 | ||||
| // Various representations that we allow
 | ||||
| enum CordRepKind { | ||||
|   CONCAT        = 0, | ||||
|   EXTERNAL      = 1, | ||||
|   SUBSTRING     = 2, | ||||
| 
 | ||||
|   // We have different tags for different sized flat arrays,
 | ||||
|   // starting with FLAT
 | ||||
|   FLAT          = 3, | ||||
| }; | ||||
| 
 | ||||
| namespace { | ||||
| 
 | ||||
| // Type used with std::allocator for allocating and deallocating
 | ||||
| // `CordRepExternal`. std::allocator is used because it opaquely handles the
 | ||||
| // different new / delete overloads available on a given platform.
 | ||||
| struct alignas(absl::cord_internal::ExternalRepAlignment()) ExternalAllocType { | ||||
|   unsigned char value[absl::cord_internal::ExternalRepAlignment()]; | ||||
| }; | ||||
| 
 | ||||
| // Returns the number of objects to pass in to std::allocator<ExternalAllocType>
 | ||||
| // allocate() and deallocate() to create enough room for `CordRepExternal` with
 | ||||
| // `releaser_size` bytes on the end.
 | ||||
| constexpr size_t GetExternalAllocNumObjects(size_t releaser_size) { | ||||
|   // Be sure to round up since `releaser_size` could be smaller than
 | ||||
|   // `sizeof(ExternalAllocType)`.
 | ||||
|   return (sizeof(CordRepExternal) + releaser_size + sizeof(ExternalAllocType) - | ||||
|           1) / | ||||
|          sizeof(ExternalAllocType); | ||||
| } | ||||
| 
 | ||||
| // Allocates enough memory for `CordRepExternal` and a releaser with size
 | ||||
| // `releaser_size` bytes.
 | ||||
| void* AllocateExternal(size_t releaser_size) { | ||||
|   return std::allocator<ExternalAllocType>().allocate( | ||||
|       GetExternalAllocNumObjects(releaser_size)); | ||||
| } | ||||
| 
 | ||||
| // Deallocates the memory for a `CordRepExternal` assuming it was allocated with
 | ||||
| // a releaser of given size and alignment.
 | ||||
| void DeallocateExternal(CordRepExternal* p, size_t releaser_size) { | ||||
|   std::allocator<ExternalAllocType>().deallocate( | ||||
|       reinterpret_cast<ExternalAllocType*>(p), | ||||
|       GetExternalAllocNumObjects(releaser_size)); | ||||
| } | ||||
| 
 | ||||
| // Returns a pointer to the type erased releaser for the given CordRepExternal.
 | ||||
| void* GetExternalReleaser(CordRepExternal* rep) { | ||||
|   return rep + 1; | ||||
| } | ||||
| 
 | ||||
| }  // namespace
 | ||||
| using ::absl::cord_internal::CONCAT; | ||||
| using ::absl::cord_internal::EXTERNAL; | ||||
| using ::absl::cord_internal::FLAT; | ||||
| using ::absl::cord_internal::SUBSTRING; | ||||
| 
 | ||||
| namespace cord_internal { | ||||
| 
 | ||||
|  | @ -289,6 +241,7 @@ static void UnrefInternal(CordRep* rep) { | |||
| 
 | ||||
|   absl::InlinedVector<CordRep*, kInlinedVectorSize> pending; | ||||
|   while (true) { | ||||
|     assert(!rep->refcount.IsImmortal()); | ||||
|     if (rep->tag == CONCAT) { | ||||
|       CordRepConcat* rep_concat = rep->concat(); | ||||
|       CordRep* right = rep_concat->right; | ||||
|  | @ -304,11 +257,8 @@ static void UnrefInternal(CordRep* rep) { | |||
|       } | ||||
|     } else if (rep->tag == EXTERNAL) { | ||||
|       CordRepExternal* rep_external = rep->external(); | ||||
|       absl::string_view data(rep_external->base, rep->length); | ||||
|       void* releaser = GetExternalReleaser(rep_external); | ||||
|       size_t releaser_size = rep_external->releaser_invoker(releaser, data); | ||||
|       rep_external->~CordRepExternal(); | ||||
|       DeallocateExternal(rep_external, releaser_size); | ||||
|       assert(rep_external->releaser_invoker != nullptr); | ||||
|       rep_external->releaser_invoker(rep_external); | ||||
|       rep = nullptr; | ||||
|     } else if (rep->tag == SUBSTRING) { | ||||
|       CordRepSubstring* rep_substring = rep->substring(); | ||||
|  | @ -458,18 +408,12 @@ static CordRep* NewTree(const char* data, | |||
| 
 | ||||
| namespace cord_internal { | ||||
| 
 | ||||
| ExternalRepReleaserPair NewExternalWithUninitializedReleaser( | ||||
|     absl::string_view data, ExternalReleaserInvoker invoker, | ||||
|     size_t releaser_size) { | ||||
| void InitializeCordRepExternal(absl::string_view data, CordRepExternal* rep) { | ||||
|   assert(!data.empty()); | ||||
| 
 | ||||
|   void* raw_rep = AllocateExternal(releaser_size); | ||||
|   auto* rep = new (raw_rep) CordRepExternal(); | ||||
|   rep->length = data.size(); | ||||
|   rep->tag = EXTERNAL; | ||||
|   rep->base = data.data(); | ||||
|   rep->releaser_invoker = invoker; | ||||
|   return {VerifyTree(rep), GetExternalReleaser(rep)}; | ||||
|   VerifyTree(rep); | ||||
| } | ||||
| 
 | ||||
| }  // namespace cord_internal
 | ||||
|  | @ -493,57 +437,55 @@ static CordRep* NewSubstring(CordRep* child, size_t offset, size_t length) { | |||
| // --------------------------------------------------------------------
 | ||||
| // Cord::InlineRep functions
 | ||||
| 
 | ||||
| // This will trigger LNK2005 in MSVC.
 | ||||
| #ifndef COMPILER_MSVC | ||||
| const unsigned char Cord::InlineRep::kMaxInline; | ||||
| #endif  // COMPILER_MSVC
 | ||||
| constexpr unsigned char Cord::InlineRep::kMaxInline; | ||||
| 
 | ||||
| inline void Cord::InlineRep::set_data(const char* data, size_t n, | ||||
|                                       bool nullify_tail) { | ||||
|   static_assert(kMaxInline == 15, "set_data is hard-coded for a length of 15"); | ||||
| 
 | ||||
|   cord_internal::SmallMemmove(data_, data, n, nullify_tail); | ||||
|   data_[kMaxInline] = static_cast<char>(n); | ||||
|   cord_internal::SmallMemmove(data_.as_chars, data, n, nullify_tail); | ||||
|   set_tagged_size(static_cast<char>(n)); | ||||
| } | ||||
| 
 | ||||
| inline char* Cord::InlineRep::set_data(size_t n) { | ||||
|   assert(n <= kMaxInline); | ||||
|   memset(data_, 0, sizeof(data_)); | ||||
|   data_[kMaxInline] = static_cast<char>(n); | ||||
|   return data_; | ||||
|   ResetToEmpty(); | ||||
|   set_tagged_size(static_cast<char>(n)); | ||||
|   return data_.as_chars; | ||||
| } | ||||
| 
 | ||||
| inline CordRep* Cord::InlineRep::force_tree(size_t extra_hint) { | ||||
|   size_t len = data_[kMaxInline]; | ||||
|   CordRep* result; | ||||
|   size_t len = tagged_size(); | ||||
|   if (len > kMaxInline) { | ||||
|     memcpy(&result, data_, sizeof(result)); | ||||
|   } else { | ||||
|     result = NewFlat(len + extra_hint); | ||||
|     result->length = len; | ||||
|     memcpy(result->data, data_, len); | ||||
|     set_tree(result); | ||||
|     return data_.as_tree.rep; | ||||
|   } | ||||
| 
 | ||||
|   CordRep* result = NewFlat(len + extra_hint); | ||||
|   result->length = len; | ||||
|   static_assert(kMinFlatLength >= sizeof(data_.as_chars), ""); | ||||
|   memcpy(result->data, data_.as_chars, sizeof(data_.as_chars)); | ||||
|   set_tree(result); | ||||
|   return result; | ||||
| } | ||||
| 
 | ||||
| inline void Cord::InlineRep::reduce_size(size_t n) { | ||||
|   size_t tag = data_[kMaxInline]; | ||||
|   size_t tag = tagged_size(); | ||||
|   assert(tag <= kMaxInline); | ||||
|   assert(tag >= n); | ||||
|   tag -= n; | ||||
|   memset(data_ + tag, 0, n); | ||||
|   data_[kMaxInline] = static_cast<char>(tag); | ||||
|   memset(data_.as_chars + tag, 0, n); | ||||
|   set_tagged_size(static_cast<char>(tag)); | ||||
| } | ||||
| 
 | ||||
| inline void Cord::InlineRep::remove_prefix(size_t n) { | ||||
|   cord_internal::SmallMemmove(data_, data_ + n, data_[kMaxInline] - n); | ||||
|   cord_internal::SmallMemmove(data_.as_chars, data_.as_chars + n, | ||||
|                               tagged_size() - n); | ||||
|   reduce_size(n); | ||||
| } | ||||
| 
 | ||||
| void Cord::InlineRep::AppendTree(CordRep* tree) { | ||||
|   if (tree == nullptr) return; | ||||
|   size_t len = data_[kMaxInline]; | ||||
|   size_t len = tagged_size(); | ||||
|   if (len == 0) { | ||||
|     set_tree(tree); | ||||
|   } else { | ||||
|  | @ -552,8 +494,8 @@ void Cord::InlineRep::AppendTree(CordRep* tree) { | |||
| } | ||||
| 
 | ||||
| void Cord::InlineRep::PrependTree(CordRep* tree) { | ||||
|   if (tree == nullptr) return; | ||||
|   size_t len = data_[kMaxInline]; | ||||
|   assert(tree != nullptr); | ||||
|   size_t len = tagged_size(); | ||||
|   if (len == 0) { | ||||
|     set_tree(tree); | ||||
|   } else { | ||||
|  | @ -609,11 +551,11 @@ void Cord::InlineRep::GetAppendRegion(char** region, size_t* size, | |||
|   } | ||||
| 
 | ||||
|   // Try to fit in the inline buffer if possible.
 | ||||
|   size_t inline_length = data_[kMaxInline]; | ||||
|   size_t inline_length = tagged_size(); | ||||
|   if (inline_length < kMaxInline && max_length <= kMaxInline - inline_length) { | ||||
|     *region = data_ + inline_length; | ||||
|     *region = data_.as_chars + inline_length; | ||||
|     *size = max_length; | ||||
|     data_[kMaxInline] = static_cast<char>(inline_length + max_length); | ||||
|     set_tagged_size(static_cast<char>(inline_length + max_length)); | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|  | @ -637,11 +579,11 @@ void Cord::InlineRep::GetAppendRegion(char** region, size_t* size) { | |||
|   const size_t max_length = std::numeric_limits<size_t>::max(); | ||||
| 
 | ||||
|   // Try to fit in the inline buffer if possible.
 | ||||
|   size_t inline_length = data_[kMaxInline]; | ||||
|   size_t inline_length = tagged_size(); | ||||
|   if (inline_length < kMaxInline) { | ||||
|     *region = data_ + inline_length; | ||||
|     *region = data_.as_chars + inline_length; | ||||
|     *size = kMaxInline - inline_length; | ||||
|     data_[kMaxInline] = kMaxInline; | ||||
|     set_tagged_size(kMaxInline); | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|  | @ -676,7 +618,7 @@ static bool RepMemoryUsageLeaf(const CordRep* rep, size_t* total_mem_usage) { | |||
| void Cord::InlineRep::AssignSlow(const Cord::InlineRep& src) { | ||||
|   ClearSlow(); | ||||
| 
 | ||||
|   memcpy(data_, src.data_, sizeof(data_)); | ||||
|   data_ = src.data_; | ||||
|   if (is_tree()) { | ||||
|     Ref(tree()); | ||||
|   } | ||||
|  | @ -686,7 +628,7 @@ void Cord::InlineRep::ClearSlow() { | |||
|   if (is_tree()) { | ||||
|     Unref(tree()); | ||||
|   } | ||||
|   memset(data_, 0, sizeof(data_)); | ||||
|   ResetToEmpty(); | ||||
| } | ||||
| 
 | ||||
| // --------------------------------------------------------------------
 | ||||
|  | @ -724,12 +666,12 @@ Cord::Cord(T&& src) { | |||
|       std::string data; | ||||
|     }; | ||||
|     const absl::string_view original_data = src; | ||||
|     CordRepExternal* rep = | ||||
|         static_cast<CordRepExternal*>(absl::cord_internal::NewExternalRep( | ||||
|             original_data, StringReleaser{std::move(src)})); | ||||
|     auto* rep = static_cast< | ||||
|         ::absl::cord_internal::CordRepExternalImpl<StringReleaser>*>( | ||||
|         absl::cord_internal::NewExternalRep( | ||||
|             original_data, StringReleaser{std::forward<T>(src)})); | ||||
|     // Moving src may have invalidated its data pointer, so adjust it.
 | ||||
|     rep->base = | ||||
|         static_cast<StringReleaser*>(GetExternalReleaser(rep))->data.data(); | ||||
|     rep->base = rep->template get<0>().data.data(); | ||||
|     contents_.set_tree(rep); | ||||
|   } | ||||
| } | ||||
|  | @ -778,7 +720,7 @@ Cord& Cord::operator=(T&& src) { | |||
|   if (src.size() <= kMaxBytesToCopy) { | ||||
|     *this = absl::string_view(src); | ||||
|   } else { | ||||
|     *this = Cord(std::move(src)); | ||||
|     *this = Cord(std::forward<T>(src)); | ||||
|   } | ||||
|   return *this; | ||||
| } | ||||
|  | @ -790,11 +732,11 @@ template Cord& Cord::operator=(std::string&& src); | |||
| void Cord::InlineRep::AppendArray(const char* src_data, size_t src_size) { | ||||
|   if (src_size == 0) return;  // memcpy(_, nullptr, 0) is undefined.
 | ||||
|   // Try to fit in the inline buffer if possible.
 | ||||
|   size_t inline_length = data_[kMaxInline]; | ||||
|   size_t inline_length = tagged_size(); | ||||
|   if (inline_length < kMaxInline && src_size <= kMaxInline - inline_length) { | ||||
|     // Append new data to embedded array
 | ||||
|     data_[kMaxInline] = static_cast<char>(inline_length + src_size); | ||||
|     memcpy(data_ + inline_length, src_data, src_size); | ||||
|     set_tagged_size(static_cast<char>(inline_length + src_size)); | ||||
|     memcpy(data_.as_chars + inline_length, src_data, src_size); | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|  | @ -817,7 +759,7 @@ void Cord::InlineRep::AppendArray(const char* src_data, size_t src_size) { | |||
|     const size_t size2 = inline_length + src_size / 10; | ||||
|     root = NewFlat(std::max<size_t>(size1, size2)); | ||||
|     appended = std::min(src_size, TagToLength(root->tag) - inline_length); | ||||
|     memcpy(root->data, data_, inline_length); | ||||
|     memcpy(root->data, data_.as_chars, inline_length); | ||||
|     memcpy(root->data + inline_length, src_data, appended); | ||||
|     root->length = inline_length + appended; | ||||
|     set_tree(root); | ||||
|  | @ -901,7 +843,7 @@ void Cord::Append(T&& src) { | |||
|   if (src.size() <= kMaxBytesToCopy) { | ||||
|     Append(absl::string_view(src)); | ||||
|   } else { | ||||
|     Append(Cord(std::move(src))); | ||||
|     Append(Cord(std::forward<T>(src))); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
|  | @ -941,7 +883,7 @@ inline void Cord::Prepend(T&& src) { | |||
|   if (src.size() <= kMaxBytesToCopy) { | ||||
|     Prepend(absl::string_view(src)); | ||||
|   } else { | ||||
|     Prepend(Cord(std::move(src))); | ||||
|     Prepend(Cord(std::forward<T>(src))); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
|  | @ -1126,7 +1068,7 @@ Cord Cord::Subcord(size_t pos, size_t new_size) const { | |||
|   } else if (new_size <= InlineRep::kMaxInline) { | ||||
|     Cord::ChunkIterator it = chunk_begin(); | ||||
|     it.AdvanceBytes(pos); | ||||
|     char* dest = sub_cord.contents_.data_; | ||||
|     char* dest = sub_cord.contents_.data_.as_chars; | ||||
|     size_t remaining_size = new_size; | ||||
|     while (remaining_size > it->size()) { | ||||
|       cord_internal::SmallMemmove(dest, it->data(), it->size()); | ||||
|  | @ -1135,7 +1077,7 @@ Cord Cord::Subcord(size_t pos, size_t new_size) const { | |||
|       ++it; | ||||
|     } | ||||
|     cord_internal::SmallMemmove(dest, it->data(), remaining_size); | ||||
|     sub_cord.contents_.data_[InlineRep::kMaxInline] = new_size; | ||||
|     sub_cord.contents_.set_tagged_size(new_size); | ||||
|   } else { | ||||
|     sub_cord.contents_.set_tree(NewSubRange(tree, pos, new_size)); | ||||
|   } | ||||
|  | @ -1324,9 +1266,9 @@ bool ComputeCompareResult<bool>(int memcmp_res) { | |||
| // Helper routine. Locates the first flat chunk of the Cord without
 | ||||
| // initializing the iterator.
 | ||||
| inline absl::string_view Cord::InlineRep::FindFlatStartPiece() const { | ||||
|   size_t n = data_[kMaxInline]; | ||||
|   size_t n = tagged_size(); | ||||
|   if (n <= kMaxInline) { | ||||
|     return absl::string_view(data_, n); | ||||
|     return absl::string_view(data_.as_chars, n); | ||||
|   } | ||||
| 
 | ||||
|   CordRep* node = tree(); | ||||
|  |  | |||
							
								
								
									
										206
									
								
								third_party/abseil_cpp/absl/strings/cord.h
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										206
									
								
								third_party/abseil_cpp/absl/strings/cord.h
									
										
									
									
										vendored
									
									
								
							|  | @ -71,7 +71,6 @@ | |||
| #include <type_traits> | ||||
| 
 | ||||
| #include "absl/base/internal/endian.h" | ||||
| #include "absl/base/internal/invoke.h" | ||||
| #include "absl/base/internal/per_thread_tls.h" | ||||
| #include "absl/base/macros.h" | ||||
| #include "absl/base/port.h" | ||||
|  | @ -80,6 +79,7 @@ | |||
| #include "absl/meta/type_traits.h" | ||||
| #include "absl/strings/internal/cord_internal.h" | ||||
| #include "absl/strings/internal/resize_uninitialized.h" | ||||
| #include "absl/strings/internal/string_constant.h" | ||||
| #include "absl/strings/string_view.h" | ||||
| #include "absl/types/optional.h" | ||||
| 
 | ||||
|  | @ -126,9 +126,9 @@ class Cord { | |||
|       absl::enable_if_t<std::is_same<T, std::string>::value, int>; | ||||
| 
 | ||||
|  public: | ||||
|   // Cord::Cord() Constructors
 | ||||
|   // Cord::Cord() Constructors.
 | ||||
| 
 | ||||
|   // Creates an empty Cord
 | ||||
|   // Creates an empty Cord.
 | ||||
|   constexpr Cord() noexcept; | ||||
| 
 | ||||
|   // Creates a Cord from an existing Cord. Cord is copyable and efficiently
 | ||||
|  | @ -154,7 +154,7 @@ class Cord { | |||
| 
 | ||||
|   // Cord::~Cord()
 | ||||
|   //
 | ||||
|   // Destructs the Cord
 | ||||
|   // Destructs the Cord.
 | ||||
|   ~Cord() { | ||||
|     if (contents_.is_tree()) DestroyCordSlow(); | ||||
|   } | ||||
|  | @ -173,10 +173,6 @@ class Cord { | |||
|   //
 | ||||
|   //   * be move constructible
 | ||||
|   //   * support `void operator()(absl::string_view) const` or `void operator()`
 | ||||
|   //   * not have alignment requirement greater than what is guaranteed by
 | ||||
|   //     `::operator new`. This alignment is dictated by
 | ||||
|   //     `alignof(std::max_align_t)` (pre-C++17 code) or
 | ||||
|   //     `__STDCPP_DEFAULT_NEW_ALIGNMENT__` (C++17 code).
 | ||||
|   //
 | ||||
|   // Example:
 | ||||
|   //
 | ||||
|  | @ -592,7 +588,7 @@ class Cord { | |||
| 
 | ||||
|   // Cord::operator[]
 | ||||
|   //
 | ||||
|   // Get the "i"th character of the Cord and returns it, provided that
 | ||||
|   // Gets the "i"th character of the Cord and returns it, provided that
 | ||||
|   // 0 <= i < Cord.size().
 | ||||
|   //
 | ||||
|   // NOTE: This routine is reasonably efficient. It is roughly
 | ||||
|  | @ -604,8 +600,8 @@ class Cord { | |||
| 
 | ||||
|   // Cord::TryFlat()
 | ||||
|   //
 | ||||
|   // If this cord's representation is a single flat array, return a
 | ||||
|   // string_view referencing that array.  Otherwise return nullopt.
 | ||||
|   // If this cord's representation is a single flat array, returns a
 | ||||
|   // string_view referencing that array.  Otherwise returns nullopt.
 | ||||
|   absl::optional<absl::string_view> TryFlat() const; | ||||
| 
 | ||||
|   // Cord::Flatten()
 | ||||
|  | @ -615,7 +611,7 @@ class Cord { | |||
|   // If the cord was already flat, the contents are not modified.
 | ||||
|   absl::string_view Flatten(); | ||||
| 
 | ||||
|   // Support absl::Cord as a sink object for absl::Format().
 | ||||
|   // Supports absl::Cord as a sink object for absl::Format().
 | ||||
|   friend void AbslFormatFlush(absl::Cord* cord, absl::string_view part) { | ||||
|     cord->Append(part); | ||||
|   } | ||||
|  | @ -629,12 +625,20 @@ class Cord { | |||
|     return c.HashFragmented(std::move(hash_state)); | ||||
|   } | ||||
| 
 | ||||
|   // Create a Cord with the contents of StringConstant<T>::value.
 | ||||
|   // No allocations will be done and no data will be copied.
 | ||||
|   // This is an INTERNAL API and subject to change or removal. This API can only
 | ||||
|   // be used by spelling absl::strings_internal::MakeStringConstant, which is
 | ||||
|   // also an internal API.
 | ||||
|   template <typename T> | ||||
|   explicit constexpr Cord(strings_internal::StringConstant<T>); | ||||
| 
 | ||||
|  private: | ||||
|   friend class CordTestPeer; | ||||
|   friend bool operator==(const Cord& lhs, const Cord& rhs); | ||||
|   friend bool operator==(const Cord& lhs, absl::string_view rhs); | ||||
| 
 | ||||
|   // Call the provided function once for each cord chunk, in order.  Unlike
 | ||||
|   // Calls the provided function once for each cord chunk, in order.  Unlike
 | ||||
|   // Chunks(), this API will not allocate memory.
 | ||||
|   void ForEachChunk(absl::FunctionRef<void(absl::string_view)>) const; | ||||
| 
 | ||||
|  | @ -649,19 +653,19 @@ class Cord { | |||
|   // InlineRep holds either a tree pointer, or an array of kMaxInline bytes.
 | ||||
|   class InlineRep { | ||||
|    public: | ||||
|     static constexpr unsigned char kMaxInline = 15; | ||||
|     static constexpr unsigned char kMaxInline = cord_internal::kMaxInline; | ||||
|     static_assert(kMaxInline >= sizeof(absl::cord_internal::CordRep*), ""); | ||||
|     // Tag byte & kMaxInline means we are storing a pointer.
 | ||||
|     static constexpr unsigned char kTreeFlag = 1 << 4; | ||||
|     // Tag byte & kProfiledFlag means we are profiling the Cord.
 | ||||
|     static constexpr unsigned char kProfiledFlag = 1 << 5; | ||||
|     static constexpr unsigned char kTreeFlag = cord_internal::kTreeFlag; | ||||
|     static constexpr unsigned char kProfiledFlag = cord_internal::kProfiledFlag; | ||||
| 
 | ||||
|     constexpr InlineRep() : data_{} {} | ||||
|     constexpr InlineRep() : data_() {} | ||||
|     InlineRep(const InlineRep& src); | ||||
|     InlineRep(InlineRep&& src); | ||||
|     InlineRep& operator=(const InlineRep& src); | ||||
|     InlineRep& operator=(InlineRep&& src) noexcept; | ||||
| 
 | ||||
|     explicit constexpr InlineRep(cord_internal::InlineData data); | ||||
| 
 | ||||
|     void Swap(InlineRep* rhs); | ||||
|     bool empty() const; | ||||
|     size_t size() const; | ||||
|  | @ -678,7 +682,7 @@ class Cord { | |||
|     void replace_tree(absl::cord_internal::CordRep* rep); | ||||
|     // Returns non-null iff was holding a pointer
 | ||||
|     absl::cord_internal::CordRep* clear(); | ||||
|     // Convert to pointer if necessary
 | ||||
|     // Converts to pointer if necessary.
 | ||||
|     absl::cord_internal::CordRep* force_tree(size_t extra_hint); | ||||
|     void reduce_size(size_t n);  // REQUIRES: holding data
 | ||||
|     void remove_prefix(size_t n);  // REQUIRES: holding data
 | ||||
|  | @ -689,16 +693,16 @@ class Cord { | |||
|     void GetAppendRegion(char** region, size_t* size, size_t max_length); | ||||
|     void GetAppendRegion(char** region, size_t* size); | ||||
|     bool IsSame(const InlineRep& other) const { | ||||
|       return memcmp(data_, other.data_, sizeof(data_)) == 0; | ||||
|       return memcmp(&data_, &other.data_, sizeof(data_)) == 0; | ||||
|     } | ||||
|     int BitwiseCompare(const InlineRep& other) const { | ||||
|       uint64_t x, y; | ||||
|       // Use memcpy to avoid anti-aliasing issues.
 | ||||
|       memcpy(&x, data_, sizeof(x)); | ||||
|       memcpy(&y, other.data_, sizeof(y)); | ||||
|       // Use memcpy to avoid aliasing issues.
 | ||||
|       memcpy(&x, &data_, sizeof(x)); | ||||
|       memcpy(&y, &other.data_, sizeof(y)); | ||||
|       if (x == y) { | ||||
|         memcpy(&x, data_ + 8, sizeof(x)); | ||||
|         memcpy(&y, other.data_ + 8, sizeof(y)); | ||||
|         memcpy(&x, reinterpret_cast<const char*>(&data_) + 8, sizeof(x)); | ||||
|         memcpy(&y, reinterpret_cast<const char*>(&other.data_) + 8, sizeof(y)); | ||||
|         if (x == y) return 0; | ||||
|       } | ||||
|       return absl::big_endian::FromHost64(x) < absl::big_endian::FromHost64(y) | ||||
|  | @ -711,16 +715,16 @@ class Cord { | |||
|       // to 15 bytes does not cause a memory allocation.
 | ||||
|       absl::strings_internal::STLStringResizeUninitialized(dst, | ||||
|                                                            sizeof(data_) - 1); | ||||
|       memcpy(&(*dst)[0], data_, sizeof(data_) - 1); | ||||
|       memcpy(&(*dst)[0], &data_, sizeof(data_) - 1); | ||||
|       // erase is faster than resize because the logic for memory allocation is
 | ||||
|       // not needed.
 | ||||
|       dst->erase(data_[kMaxInline]); | ||||
|       dst->erase(tagged_size()); | ||||
|     } | ||||
| 
 | ||||
|     // Copies the inline contents into `dst`. Assumes the cord is not empty.
 | ||||
|     void CopyToArray(char* dst) const; | ||||
| 
 | ||||
|     bool is_tree() const { return data_[kMaxInline] > kMaxInline; } | ||||
|     bool is_tree() const { return tagged_size() > kMaxInline; } | ||||
| 
 | ||||
|    private: | ||||
|     friend class Cord; | ||||
|  | @ -729,21 +733,29 @@ class Cord { | |||
|     // Unrefs the tree, stops profiling, and zeroes the contents
 | ||||
|     void ClearSlow(); | ||||
| 
 | ||||
|     // If the data has length <= kMaxInline, we store it in data_[0..len-1],
 | ||||
|     // and store the length in data_[kMaxInline].  Else we store it in a tree
 | ||||
|     // and store a pointer to that tree in data_[0..sizeof(CordRep*)-1].
 | ||||
|     alignas(absl::cord_internal::CordRep*) char data_[kMaxInline + 1]; | ||||
|     void ResetToEmpty() { data_ = {}; } | ||||
| 
 | ||||
|     // This uses reinterpret_cast instead of the union to avoid accessing the
 | ||||
|     // inactive union element. The tagged size is not a common prefix.
 | ||||
|     void set_tagged_size(char new_tag) { | ||||
|       reinterpret_cast<char*>(&data_)[kMaxInline] = new_tag; | ||||
|     } | ||||
|     char tagged_size() const { | ||||
|       return reinterpret_cast<const char*>(&data_)[kMaxInline]; | ||||
|     } | ||||
| 
 | ||||
|     cord_internal::InlineData data_; | ||||
|   }; | ||||
|   InlineRep contents_; | ||||
| 
 | ||||
|   // Helper for MemoryUsage()
 | ||||
|   // Helper for MemoryUsage().
 | ||||
|   static size_t MemoryUsageAux(const absl::cord_internal::CordRep* rep); | ||||
| 
 | ||||
|   // Helper for GetFlat() and TryFlat()
 | ||||
|   // Helper for GetFlat() and TryFlat().
 | ||||
|   static bool GetFlatAux(absl::cord_internal::CordRep* rep, | ||||
|                          absl::string_view* fragment); | ||||
| 
 | ||||
|   // Helper for ForEachChunk()
 | ||||
|   // Helper for ForEachChunk().
 | ||||
|   static void ForEachChunkAux( | ||||
|       absl::cord_internal::CordRep* rep, | ||||
|       absl::FunctionRef<void(absl::string_view)> callback); | ||||
|  | @ -772,11 +784,11 @@ class Cord { | |||
|   absl::cord_internal::CordRep* TakeRep() const&; | ||||
|   absl::cord_internal::CordRep* TakeRep() &&; | ||||
| 
 | ||||
|   // Helper for Append()
 | ||||
|   // Helper for Append().
 | ||||
|   template <typename C> | ||||
|   void AppendImpl(C&& src); | ||||
| 
 | ||||
|   // Helper for AbslHashValue()
 | ||||
|   // Helper for AbslHashValue().
 | ||||
|   template <typename H> | ||||
|   H HashFragmented(H hash_state) const { | ||||
|     typename H::AbslInternalPiecewiseCombiner combiner; | ||||
|  | @ -842,47 +854,15 @@ inline void SmallMemmove(char* dst, const char* src, size_t n, | |||
|   } | ||||
| } | ||||
| 
 | ||||
| struct ExternalRepReleaserPair { | ||||
|   CordRep* rep; | ||||
|   void* releaser_address; | ||||
| }; | ||||
| 
 | ||||
| // Allocates a new external `CordRep` and returns a pointer to it and a pointer
 | ||||
| // to `releaser_size` bytes where the desired releaser can be constructed.
 | ||||
| // Does non-template-specific `CordRepExternal` initialization.
 | ||||
| // Expects `data` to be non-empty.
 | ||||
| ExternalRepReleaserPair NewExternalWithUninitializedReleaser( | ||||
|     absl::string_view data, ExternalReleaserInvoker invoker, | ||||
|     size_t releaser_size); | ||||
| 
 | ||||
| struct Rank1 {}; | ||||
| struct Rank0 : Rank1 {}; | ||||
| 
 | ||||
| template <typename Releaser, typename = ::absl::base_internal::InvokeT< | ||||
|                                  Releaser, absl::string_view>> | ||||
| void InvokeReleaser(Rank0, Releaser&& releaser, absl::string_view data) { | ||||
|   ::absl::base_internal::Invoke(std::forward<Releaser>(releaser), data); | ||||
| } | ||||
| 
 | ||||
| template <typename Releaser, | ||||
|           typename = ::absl::base_internal::InvokeT<Releaser>> | ||||
| void InvokeReleaser(Rank1, Releaser&& releaser, absl::string_view) { | ||||
|   ::absl::base_internal::Invoke(std::forward<Releaser>(releaser)); | ||||
| } | ||||
| void InitializeCordRepExternal(absl::string_view data, CordRepExternal* rep); | ||||
| 
 | ||||
| // Creates a new `CordRep` that owns `data` and `releaser` and returns a pointer
 | ||||
| // to it, or `nullptr` if `data` was empty.
 | ||||
| template <typename Releaser> | ||||
| // NOLINTNEXTLINE - suppress clang-tidy raw pointer return.
 | ||||
| CordRep* NewExternalRep(absl::string_view data, Releaser&& releaser) { | ||||
|   static_assert( | ||||
| #if defined(__STDCPP_DEFAULT_NEW_ALIGNMENT__) | ||||
|       alignof(Releaser) <= __STDCPP_DEFAULT_NEW_ALIGNMENT__, | ||||
| #else | ||||
|       alignof(Releaser) <= alignof(max_align_t), | ||||
| #endif | ||||
|       "Releasers with alignment requirement greater than what is returned by " | ||||
|       "default `::operator new()` are not supported."); | ||||
| 
 | ||||
|   using ReleaserType = absl::decay_t<Releaser>; | ||||
|   if (data.empty()) { | ||||
|     // Never create empty external nodes.
 | ||||
|  | @ -891,18 +871,10 @@ CordRep* NewExternalRep(absl::string_view data, Releaser&& releaser) { | |||
|     return nullptr; | ||||
|   } | ||||
| 
 | ||||
|   auto releaser_invoker = [](void* type_erased_releaser, absl::string_view d) { | ||||
|     auto* my_releaser = static_cast<ReleaserType*>(type_erased_releaser); | ||||
|     InvokeReleaser(Rank0{}, std::move(*my_releaser), d); | ||||
|     my_releaser->~ReleaserType(); | ||||
|     return sizeof(Releaser); | ||||
|   }; | ||||
| 
 | ||||
|   ExternalRepReleaserPair external = NewExternalWithUninitializedReleaser( | ||||
|       data, releaser_invoker, sizeof(releaser)); | ||||
|   ::new (external.releaser_address) | ||||
|       ReleaserType(std::forward<Releaser>(releaser)); | ||||
|   return external.rep; | ||||
|   CordRepExternal* rep = new CordRepExternalImpl<ReleaserType>( | ||||
|       std::forward<Releaser>(releaser), 0); | ||||
|   InitializeCordRepExternal(data, rep); | ||||
|   return rep; | ||||
| } | ||||
| 
 | ||||
| // Overload for function reference types that dispatches using a function
 | ||||
|  | @ -923,13 +895,16 @@ Cord MakeCordFromExternal(absl::string_view data, Releaser&& releaser) { | |||
|   return cord; | ||||
| } | ||||
| 
 | ||||
| constexpr Cord::InlineRep::InlineRep(cord_internal::InlineData data) | ||||
|     : data_(data) {} | ||||
| 
 | ||||
| inline Cord::InlineRep::InlineRep(const Cord::InlineRep& src) { | ||||
|   cord_internal::SmallMemmove(data_, src.data_, sizeof(data_)); | ||||
|   data_ = src.data_; | ||||
| } | ||||
| 
 | ||||
| inline Cord::InlineRep::InlineRep(Cord::InlineRep&& src) { | ||||
|   memcpy(data_, src.data_, sizeof(data_)); | ||||
|   memset(src.data_, 0, sizeof(data_)); | ||||
|   data_ = src.data_; | ||||
|   src.ResetToEmpty(); | ||||
| } | ||||
| 
 | ||||
| inline Cord::InlineRep& Cord::InlineRep::operator=(const Cord::InlineRep& src) { | ||||
|  | @ -937,7 +912,7 @@ inline Cord::InlineRep& Cord::InlineRep::operator=(const Cord::InlineRep& src) { | |||
|     return *this; | ||||
|   } | ||||
|   if (!is_tree() && !src.is_tree()) { | ||||
|     cord_internal::SmallMemmove(data_, src.data_, sizeof(data_)); | ||||
|     data_ = src.data_; | ||||
|     return *this; | ||||
|   } | ||||
|   AssignSlow(src); | ||||
|  | @ -949,8 +924,8 @@ inline Cord::InlineRep& Cord::InlineRep::operator=( | |||
|   if (is_tree()) { | ||||
|     ClearSlow(); | ||||
|   } | ||||
|   memcpy(data_, src.data_, sizeof(data_)); | ||||
|   memset(src.data_, 0, sizeof(data_)); | ||||
|   data_ = src.data_; | ||||
|   src.ResetToEmpty(); | ||||
|   return *this; | ||||
| } | ||||
| 
 | ||||
|  | @ -959,43 +934,39 @@ inline void Cord::InlineRep::Swap(Cord::InlineRep* rhs) { | |||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   Cord::InlineRep tmp; | ||||
|   cord_internal::SmallMemmove(tmp.data_, data_, sizeof(data_)); | ||||
|   cord_internal::SmallMemmove(data_, rhs->data_, sizeof(data_)); | ||||
|   cord_internal::SmallMemmove(rhs->data_, tmp.data_, sizeof(data_)); | ||||
|   std::swap(data_, rhs->data_); | ||||
| } | ||||
| 
 | ||||
| inline const char* Cord::InlineRep::data() const { | ||||
|   return is_tree() ? nullptr : data_; | ||||
|   return is_tree() ? nullptr : data_.as_chars; | ||||
| } | ||||
| 
 | ||||
| inline absl::cord_internal::CordRep* Cord::InlineRep::tree() const { | ||||
|   if (is_tree()) { | ||||
|     absl::cord_internal::CordRep* rep; | ||||
|     memcpy(&rep, data_, sizeof(rep)); | ||||
|     return rep; | ||||
|     return data_.as_tree.rep; | ||||
|   } else { | ||||
|     return nullptr; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| inline bool Cord::InlineRep::empty() const { return data_[kMaxInline] == 0; } | ||||
| inline bool Cord::InlineRep::empty() const { return tagged_size() == 0; } | ||||
| 
 | ||||
| inline size_t Cord::InlineRep::size() const { | ||||
|   const char tag = data_[kMaxInline]; | ||||
|   const char tag = tagged_size(); | ||||
|   if (tag <= kMaxInline) return tag; | ||||
|   return static_cast<size_t>(tree()->length); | ||||
| } | ||||
| 
 | ||||
| inline void Cord::InlineRep::set_tree(absl::cord_internal::CordRep* rep) { | ||||
|   if (rep == nullptr) { | ||||
|     memset(data_, 0, sizeof(data_)); | ||||
|     ResetToEmpty(); | ||||
|   } else { | ||||
|     bool was_tree = is_tree(); | ||||
|     memcpy(data_, &rep, sizeof(rep)); | ||||
|     memset(data_ + sizeof(rep), 0, sizeof(data_) - sizeof(rep) - 1); | ||||
|     data_.as_tree = {rep, {}, tagged_size()}; | ||||
|     if (!was_tree) { | ||||
|       data_[kMaxInline] = kTreeFlag; | ||||
|       // If we were not a tree already, set the tag.
 | ||||
|       // Otherwise, leave it alone because it might have the profile bit on.
 | ||||
|       set_tagged_size(kTreeFlag); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | @ -1006,29 +977,36 @@ inline void Cord::InlineRep::replace_tree(absl::cord_internal::CordRep* rep) { | |||
|     set_tree(rep); | ||||
|     return; | ||||
|   } | ||||
|   memcpy(data_, &rep, sizeof(rep)); | ||||
|   memset(data_ + sizeof(rep), 0, sizeof(data_) - sizeof(rep) - 1); | ||||
|   data_.as_tree = {rep, {}, tagged_size()}; | ||||
| } | ||||
| 
 | ||||
| inline absl::cord_internal::CordRep* Cord::InlineRep::clear() { | ||||
|   const char tag = data_[kMaxInline]; | ||||
|   absl::cord_internal::CordRep* result = nullptr; | ||||
|   if (tag > kMaxInline) { | ||||
|     memcpy(&result, data_, sizeof(result)); | ||||
|   } | ||||
|   memset(data_, 0, sizeof(data_));  // Clear the cord
 | ||||
|   absl::cord_internal::CordRep* result = tree(); | ||||
|   ResetToEmpty(); | ||||
|   return result; | ||||
| } | ||||
| 
 | ||||
| inline void Cord::InlineRep::CopyToArray(char* dst) const { | ||||
|   assert(!is_tree()); | ||||
|   size_t n = data_[kMaxInline]; | ||||
|   size_t n = tagged_size(); | ||||
|   assert(n != 0); | ||||
|   cord_internal::SmallMemmove(dst, data_, n); | ||||
|   cord_internal::SmallMemmove(dst, data_.as_chars, n); | ||||
| } | ||||
| 
 | ||||
| constexpr inline Cord::Cord() noexcept {} | ||||
| 
 | ||||
| template <typename T> | ||||
| constexpr Cord::Cord(strings_internal::StringConstant<T>) | ||||
|     : contents_(strings_internal::StringConstant<T>::value.size() <= | ||||
|                         cord_internal::kMaxInline | ||||
|                     ? cord_internal::InlineData( | ||||
|                           strings_internal::StringConstant<T>::value) | ||||
|                     : cord_internal::InlineData(cord_internal::AsTree{ | ||||
|                           &cord_internal::ConstInitExternalStorage< | ||||
|                               strings_internal::StringConstant<T>>::value, | ||||
|                           {}, | ||||
|                           cord_internal::kTreeFlag})) {} | ||||
| 
 | ||||
| inline Cord& Cord::operator=(const Cord& x) { | ||||
|   contents_ = x.contents_; | ||||
|   return *this; | ||||
|  |  | |||
							
								
								
									
										96
									
								
								third_party/abseil_cpp/absl/strings/cord_test.cc
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										96
									
								
								third_party/abseil_cpp/absl/strings/cord_test.cc
									
										
									
									
										vendored
									
									
								
							|  | @ -1,3 +1,17 @@ | |||
| // Copyright 2020 The Abseil Authors.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //      https://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| #include "absl/strings/cord.h" | ||||
| 
 | ||||
| #include <algorithm> | ||||
|  | @ -167,6 +181,8 @@ class CordTestPeer { | |||
|       const Cord& c, absl::FunctionRef<void(absl::string_view)> callback) { | ||||
|     c.ForEachChunk(callback); | ||||
|   } | ||||
| 
 | ||||
|   static bool IsTree(const Cord& c) { return c.contents_.is_tree(); } | ||||
| }; | ||||
| 
 | ||||
| ABSL_NAMESPACE_END | ||||
|  | @ -1613,3 +1629,83 @@ TEST(CordDeathTest, Hardening) { | |||
|   EXPECT_DEATH_IF_SUPPORTED(static_cast<void>(cord.chunk_end()->empty()), ""); | ||||
|   EXPECT_DEATH_IF_SUPPORTED(++cord.chunk_end(), ""); | ||||
| } | ||||
| 
 | ||||
| class AfterExitCordTester { | ||||
|  public: | ||||
|   bool Set(absl::Cord* cord, absl::string_view expected) { | ||||
|     cord_ = cord; | ||||
|     expected_ = expected; | ||||
|     return true; | ||||
|   } | ||||
| 
 | ||||
|   ~AfterExitCordTester() { | ||||
|     EXPECT_EQ(*cord_, expected_); | ||||
|   } | ||||
|  private: | ||||
|   absl::Cord* cord_; | ||||
|   absl::string_view expected_; | ||||
| }; | ||||
| 
 | ||||
| template <typename Str> | ||||
| void TestConstinitConstructor(Str) { | ||||
|   const auto expected = Str::value; | ||||
|   // Defined before `cord` to be destroyed after it.
 | ||||
|   static AfterExitCordTester exit_tester;  // NOLINT
 | ||||
|   ABSL_CONST_INIT static absl::Cord cord(Str{});  // NOLINT
 | ||||
|   static bool init_exit_tester = exit_tester.Set(&cord, expected); | ||||
|   (void)init_exit_tester; | ||||
| 
 | ||||
|   EXPECT_EQ(cord, expected); | ||||
|   // Copy the object and test the copy, and the original.
 | ||||
|   { | ||||
|     absl::Cord copy = cord; | ||||
|     EXPECT_EQ(copy, expected); | ||||
|   } | ||||
|   // The original still works
 | ||||
|   EXPECT_EQ(cord, expected); | ||||
| 
 | ||||
|   // Try making adding more structure to the tree.
 | ||||
|   { | ||||
|     absl::Cord copy = cord; | ||||
|     std::string expected_copy(expected); | ||||
|     for (int i = 0; i < 10; ++i) { | ||||
|       copy.Append(cord); | ||||
|       absl::StrAppend(&expected_copy, expected); | ||||
|       EXPECT_EQ(copy, expected_copy); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   // Make sure we are using the right branch during constant evaluation.
 | ||||
|   EXPECT_EQ(absl::CordTestPeer::IsTree(cord), cord.size() >= 16); | ||||
| 
 | ||||
|   for (int i = 0; i < 10; ++i) { | ||||
|     // Make a few more Cords from the same global rep.
 | ||||
|     // This tests what happens when the refcount for it gets below 1.
 | ||||
|     EXPECT_EQ(expected, absl::Cord(Str{})); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| constexpr int SimpleStrlen(const char* p) { | ||||
|   return *p ? 1 + SimpleStrlen(p + 1) : 0; | ||||
| } | ||||
| 
 | ||||
| struct ShortView { | ||||
|   constexpr absl::string_view operator()() const { | ||||
|     return absl::string_view("SSO string", SimpleStrlen("SSO string")); | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| struct LongView { | ||||
|   constexpr absl::string_view operator()() const { | ||||
|     return absl::string_view("String that does not fit SSO.", | ||||
|                              SimpleStrlen("String that does not fit SSO.")); | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| TEST(Cord, ConstinitConstructor) { | ||||
|   TestConstinitConstructor( | ||||
|       absl::strings_internal::MakeStringConstant(ShortView{})); | ||||
|   TestConstinitConstructor( | ||||
|       absl::strings_internal::MakeStringConstant(LongView{})); | ||||
| } | ||||
|  |  | |||
|  | @ -137,7 +137,7 @@ bool CUnescapeInternal(absl::string_view source, bool leave_nulls_escaped, | |||
|             // Copy the escape sequence for the null character
 | ||||
|             const ptrdiff_t octal_size = p + 1 - octal_start; | ||||
|             *d++ = '\\'; | ||||
|             memcpy(d, octal_start, octal_size); | ||||
|             memmove(d, octal_start, octal_size); | ||||
|             d += octal_size; | ||||
|             break; | ||||
|           } | ||||
|  | @ -170,7 +170,7 @@ bool CUnescapeInternal(absl::string_view source, bool leave_nulls_escaped, | |||
|             // Copy the escape sequence for the null character
 | ||||
|             const ptrdiff_t hex_size = p + 1 - hex_start; | ||||
|             *d++ = '\\'; | ||||
|             memcpy(d, hex_start, hex_size); | ||||
|             memmove(d, hex_start, hex_size); | ||||
|             d += hex_size; | ||||
|             break; | ||||
|           } | ||||
|  | @ -203,7 +203,7 @@ bool CUnescapeInternal(absl::string_view source, bool leave_nulls_escaped, | |||
|           if ((rune == 0) && leave_nulls_escaped) { | ||||
|             // Copy the escape sequence for the null character
 | ||||
|             *d++ = '\\'; | ||||
|             memcpy(d, hex_start, 5);  // u0000
 | ||||
|             memmove(d, hex_start, 5);  // u0000
 | ||||
|             d += 5; | ||||
|             break; | ||||
|           } | ||||
|  | @ -251,7 +251,7 @@ bool CUnescapeInternal(absl::string_view source, bool leave_nulls_escaped, | |||
|           if ((rune == 0) && leave_nulls_escaped) { | ||||
|             // Copy the escape sequence for the null character
 | ||||
|             *d++ = '\\'; | ||||
|             memcpy(d, hex_start, 9);  // U00000000
 | ||||
|             memmove(d, hex_start, 9);  // U00000000
 | ||||
|             d += 9; | ||||
|             break; | ||||
|           } | ||||
|  |  | |||
|  | @ -69,6 +69,61 @@ TEST(BigUnsigned, ShiftLeft) { | |||
|     // And we should have fully rotated all bits off by now:
 | ||||
|     EXPECT_EQ(a, BigUnsigned<84>(0u)); | ||||
|   } | ||||
|   { | ||||
|     // Bit shifting large and small numbers by large and small offsets.
 | ||||
|     // Intended to exercise bounds-checking corner on ShiftLeft() (directly
 | ||||
|     // and under asan).
 | ||||
| 
 | ||||
|     // 2**(32*84)-1
 | ||||
|     const BigUnsigned<84> all_bits_one( | ||||
|         "1474444211396924248063325089479706787923460402125687709454567433186613" | ||||
|         "6228083464060749874845919674257665016359189106695900028098437021384227" | ||||
|         "3285029708032466536084583113729486015826557532750465299832071590813090" | ||||
|         "2011853039837649252477307070509704043541368002938784757296893793903797" | ||||
|         "8180292336310543540677175225040919704702800559606097685920595947397024" | ||||
|         "8303316808753252115729411497720357971050627997031988036134171378490368" | ||||
|         "6008000778741115399296162550786288457245180872759047016734959330367829" | ||||
|         "5235612397427686310674725251378116268607113017720538636924549612987647" | ||||
|         "5767411074510311386444547332882472126067840027882117834454260409440463" | ||||
|         "9345147252664893456053258463203120637089916304618696601333953616715125" | ||||
|         "2115882482473279040772264257431663818610405673876655957323083702713344" | ||||
|         "4201105427930770976052393421467136557055"); | ||||
|     const BigUnsigned<84> zero(0u); | ||||
|     const BigUnsigned<84> one(1u); | ||||
|     // in bounds shifts
 | ||||
|     for (int i = 1; i < 84*32; ++i) { | ||||
|       // shifting all_bits_one to the left should result in a smaller number,
 | ||||
|       // since the high bits rotate off and the low bits are replaced with
 | ||||
|       // zeroes.
 | ||||
|       BigUnsigned<84> big_shifted = all_bits_one; | ||||
|       big_shifted.ShiftLeft(i); | ||||
|       EXPECT_GT(all_bits_one, big_shifted); | ||||
|       // Shifting 1 to the left should instead result in a larger number.
 | ||||
|       BigUnsigned<84> small_shifted = one; | ||||
|       small_shifted.ShiftLeft(i); | ||||
|       EXPECT_LT(one, small_shifted); | ||||
|     } | ||||
|     // Shifting by zero or a negative number has no effect
 | ||||
|     for (int no_op_shift : {0, -1, -84 * 32, std::numeric_limits<int>::min()}) { | ||||
|       BigUnsigned<84> big_shifted = all_bits_one; | ||||
|       big_shifted.ShiftLeft(no_op_shift); | ||||
|       EXPECT_EQ(all_bits_one, big_shifted); | ||||
|       BigUnsigned<84> small_shifted = one; | ||||
|       big_shifted.ShiftLeft(no_op_shift); | ||||
|       EXPECT_EQ(one, small_shifted); | ||||
|     } | ||||
|     // Shifting by an amount greater than the number of bits should result in
 | ||||
|     // zero.
 | ||||
|     for (int out_of_bounds_shift : | ||||
|          {84 * 32, 84 * 32 + 1, std::numeric_limits<int>::max()}) { | ||||
|       BigUnsigned<84> big_shifted = all_bits_one; | ||||
|       big_shifted.ShiftLeft(out_of_bounds_shift); | ||||
|       EXPECT_EQ(zero, big_shifted); | ||||
|       BigUnsigned<84> small_shifted = one; | ||||
|       small_shifted.ShiftLeft(out_of_bounds_shift); | ||||
|       EXPECT_EQ(zero, small_shifted); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| TEST(BigUnsigned, MultiplyByUint32) { | ||||
|  |  | |||
|  | @ -246,8 +246,8 @@ constexpr int DigitMagnitude<16>() { | |||
| // ConsumeDigits does not protect against overflow on *out; max_digits must
 | ||||
| // be chosen with respect to type T to avoid the possibility of overflow.
 | ||||
| template <int base, typename T> | ||||
| std::size_t ConsumeDigits(const char* begin, const char* end, int max_digits, | ||||
|                           T* out, bool* dropped_nonzero_digit) { | ||||
| int ConsumeDigits(const char* begin, const char* end, int max_digits, T* out, | ||||
|                   bool* dropped_nonzero_digit) { | ||||
|   if (base == 10) { | ||||
|     assert(max_digits <= std::numeric_limits<T>::digits10); | ||||
|   } else if (base == 16) { | ||||
|  | @ -282,7 +282,7 @@ std::size_t ConsumeDigits(const char* begin, const char* end, int max_digits, | |||
|     *dropped_nonzero_digit = true; | ||||
|   } | ||||
|   *out = accumulator; | ||||
|   return begin - original_begin; | ||||
|   return static_cast<int>(begin - original_begin); | ||||
| } | ||||
| 
 | ||||
| // Returns true if `v` is one of the chars allowed inside parentheses following
 | ||||
|  | @ -372,7 +372,7 @@ strings_internal::ParsedFloat ParseFloat(const char* begin, const char* end, | |||
| 
 | ||||
|   int exponent_adjustment = 0; | ||||
|   bool mantissa_is_inexact = false; | ||||
|   std::size_t pre_decimal_digits = ConsumeDigits<base>( | ||||
|   int pre_decimal_digits = ConsumeDigits<base>( | ||||
|       begin, end, MantissaDigitsMax<base>(), &mantissa, &mantissa_is_inexact); | ||||
|   begin += pre_decimal_digits; | ||||
|   int digits_left; | ||||
|  | @ -398,14 +398,14 @@ strings_internal::ParsedFloat ParseFloat(const char* begin, const char* end, | |||
|       while (begin < end && *begin == '0') { | ||||
|         ++begin; | ||||
|       } | ||||
|       std::size_t zeros_skipped = begin - begin_zeros; | ||||
|       int zeros_skipped = static_cast<int>(begin - begin_zeros); | ||||
|       if (zeros_skipped >= DigitLimit<base>()) { | ||||
|         // refuse to parse pathological inputs
 | ||||
|         return result; | ||||
|       } | ||||
|       exponent_adjustment -= static_cast<int>(zeros_skipped); | ||||
|     } | ||||
|     std::size_t post_decimal_digits = ConsumeDigits<base>( | ||||
|     int post_decimal_digits = ConsumeDigits<base>( | ||||
|         begin, end, digits_left, &mantissa, &mantissa_is_inexact); | ||||
|     begin += post_decimal_digits; | ||||
| 
 | ||||
|  |  | |||
|  | @ -21,6 +21,8 @@ | |||
| #include <cstdint> | ||||
| #include <type_traits> | ||||
| 
 | ||||
| #include "absl/base/internal/invoke.h" | ||||
| #include "absl/container/internal/compressed_tuple.h" | ||||
| #include "absl/meta/type_traits.h" | ||||
| #include "absl/strings/string_view.h" | ||||
| 
 | ||||
|  | @ -31,14 +33,17 @@ namespace cord_internal { | |||
| // Wraps std::atomic for reference counting.
 | ||||
| class Refcount { | ||||
|  public: | ||||
|   Refcount() : count_{1} {} | ||||
|   ~Refcount() {} | ||||
|   constexpr Refcount() : count_{kRefIncrement} {} | ||||
|   struct Immortal {}; | ||||
|   explicit constexpr Refcount(Immortal) : count_(kImmortalTag) {} | ||||
| 
 | ||||
|   // Increments the reference count by 1. Imposes no memory ordering.
 | ||||
|   inline void Increment() { count_.fetch_add(1, std::memory_order_relaxed); } | ||||
|   // Increments the reference count. Imposes no memory ordering.
 | ||||
|   inline void Increment() { | ||||
|     count_.fetch_add(kRefIncrement, std::memory_order_relaxed); | ||||
|   } | ||||
| 
 | ||||
|   // Asserts that the current refcount is greater than 0. If the refcount is
 | ||||
|   // greater than 1, decrements the reference count by 1.
 | ||||
|   // greater than 1, decrements the reference count.
 | ||||
|   //
 | ||||
|   // Returns false if there are no references outstanding; true otherwise.
 | ||||
|   // Inserts barriers to ensure that state written before this method returns
 | ||||
|  | @ -46,19 +51,24 @@ class Refcount { | |||
|   // false.
 | ||||
|   inline bool Decrement() { | ||||
|     int32_t refcount = count_.load(std::memory_order_acquire); | ||||
|     assert(refcount > 0); | ||||
|     return refcount != 1 && count_.fetch_sub(1, std::memory_order_acq_rel) != 1; | ||||
|     assert(refcount > 0 || refcount & kImmortalTag); | ||||
|     return refcount != kRefIncrement && | ||||
|            count_.fetch_sub(kRefIncrement, std::memory_order_acq_rel) != | ||||
|                kRefIncrement; | ||||
|   } | ||||
| 
 | ||||
|   // Same as Decrement but expect that refcount is greater than 1.
 | ||||
|   inline bool DecrementExpectHighRefcount() { | ||||
|     int32_t refcount = count_.fetch_sub(1, std::memory_order_acq_rel); | ||||
|     assert(refcount > 0); | ||||
|     return refcount != 1; | ||||
|     int32_t refcount = | ||||
|         count_.fetch_sub(kRefIncrement, std::memory_order_acq_rel); | ||||
|     assert(refcount > 0 || refcount & kImmortalTag); | ||||
|     return refcount != kRefIncrement; | ||||
|   } | ||||
| 
 | ||||
|   // Returns the current reference count using acquire semantics.
 | ||||
|   inline int32_t Get() const { return count_.load(std::memory_order_acquire); } | ||||
|   inline int32_t Get() const { | ||||
|     return count_.load(std::memory_order_acquire) >> kImmortalShift; | ||||
|   } | ||||
| 
 | ||||
|   // Returns whether the atomic integer is 1.
 | ||||
|   // If the reference count is used in the conventional way, a
 | ||||
|  | @ -68,9 +78,27 @@ class Refcount { | |||
|   // performs the memory barrier needed for the owning thread
 | ||||
|   // to act on the object, knowing that it has exclusive access to the
 | ||||
|   // object.
 | ||||
|   inline bool IsOne() { return count_.load(std::memory_order_acquire) == 1; } | ||||
|   inline bool IsOne() { | ||||
|     return count_.load(std::memory_order_acquire) == kRefIncrement; | ||||
|   } | ||||
| 
 | ||||
|   bool IsImmortal() const { | ||||
|     return (count_.load(std::memory_order_relaxed) & kImmortalTag) != 0; | ||||
|   } | ||||
| 
 | ||||
|  private: | ||||
|   // We reserve the bottom bit to tag a reference count as immortal.
 | ||||
|   // By making it `1` we ensure that we never reach `0` when adding/subtracting
 | ||||
|   // `2`, thus it never looks as if it should be destroyed.
 | ||||
|   // These are used for the StringConstant constructor where we do not increase
 | ||||
|   // the refcount at construction time (due to constinit requirements) but we
 | ||||
|   // will still decrease it at destruction time to avoid branching on Unref.
 | ||||
|   enum { | ||||
|     kImmortalShift = 1, | ||||
|     kRefIncrement = 1 << kImmortalShift, | ||||
|     kImmortalTag = kRefIncrement - 1 | ||||
|   }; | ||||
| 
 | ||||
|   std::atomic<int32_t> count_; | ||||
| }; | ||||
| 
 | ||||
|  | @ -83,7 +111,22 @@ struct CordRepConcat; | |||
| struct CordRepSubstring; | ||||
| struct CordRepExternal; | ||||
| 
 | ||||
| // Various representations that we allow
 | ||||
| enum CordRepKind { | ||||
|   CONCAT        = 0, | ||||
|   EXTERNAL      = 1, | ||||
|   SUBSTRING     = 2, | ||||
| 
 | ||||
|   // We have different tags for different sized flat arrays,
 | ||||
|   // starting with FLAT
 | ||||
|   FLAT          = 3, | ||||
| }; | ||||
| 
 | ||||
| struct CordRep { | ||||
|   CordRep() = default; | ||||
|   constexpr CordRep(Refcount::Immortal immortal, size_t l) | ||||
|       : length(l), refcount(immortal), tag(EXTERNAL), data{} {} | ||||
| 
 | ||||
|   // The following three fields have to be less than 32 bytes since
 | ||||
|   // that is the smallest supported flat node size.
 | ||||
|   size_t length; | ||||
|  | @ -114,35 +157,112 @@ struct CordRepSubstring : public CordRep { | |||
|   CordRep* child; | ||||
| }; | ||||
| 
 | ||||
| // TODO(strel): replace the following logic (and related functions in cord.cc)
 | ||||
| // with container_internal::Layout.
 | ||||
| 
 | ||||
| // Alignment requirement for CordRepExternal so that the type erased releaser
 | ||||
| // will be stored at a suitably aligned address.
 | ||||
| constexpr size_t ExternalRepAlignment() { | ||||
| #if defined(__STDCPP_DEFAULT_NEW_ALIGNMENT__) | ||||
|   return __STDCPP_DEFAULT_NEW_ALIGNMENT__; | ||||
| #else | ||||
|   return alignof(max_align_t); | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| // Type for function pointer that will invoke and destroy the type-erased
 | ||||
| // releaser function object. Accepts a pointer to the releaser and the
 | ||||
| // `string_view` that were passed in to `NewExternalRep` below. The return value
 | ||||
| // is the size of the `Releaser` type.
 | ||||
| using ExternalReleaserInvoker = size_t (*)(void*, absl::string_view); | ||||
| // Type for function pointer that will invoke the releaser function and also
 | ||||
| // delete the `CordRepExternalImpl` corresponding to the passed in
 | ||||
| // `CordRepExternal`.
 | ||||
| using ExternalReleaserInvoker = void (*)(CordRepExternal*); | ||||
| 
 | ||||
| // External CordReps are allocated together with a type erased releaser. The
 | ||||
| // releaser is stored in the memory directly following the CordRepExternal.
 | ||||
| struct alignas(ExternalRepAlignment()) CordRepExternal : public CordRep { | ||||
| struct CordRepExternal : public CordRep { | ||||
|   CordRepExternal() = default; | ||||
|   explicit constexpr CordRepExternal(absl::string_view str) | ||||
|       : CordRep(Refcount::Immortal{}, str.size()), | ||||
|         base(str.data()), | ||||
|         releaser_invoker(nullptr) {} | ||||
| 
 | ||||
|   const char* base; | ||||
|   // Pointer to function that knows how to call and destroy the releaser.
 | ||||
|   ExternalReleaserInvoker releaser_invoker; | ||||
| }; | ||||
| 
 | ||||
| // TODO(strel): look into removing, it doesn't seem like anything relies on this
 | ||||
| static_assert(sizeof(CordRepConcat) == sizeof(CordRepSubstring), ""); | ||||
| struct Rank1 {}; | ||||
| struct Rank0 : Rank1 {}; | ||||
| 
 | ||||
| template <typename Releaser, typename = ::absl::base_internal::invoke_result_t< | ||||
|                                  Releaser, absl::string_view>> | ||||
| void InvokeReleaser(Rank0, Releaser&& releaser, absl::string_view data) { | ||||
|   ::absl::base_internal::invoke(std::forward<Releaser>(releaser), data); | ||||
| } | ||||
| 
 | ||||
| template <typename Releaser, | ||||
|           typename = ::absl::base_internal::invoke_result_t<Releaser>> | ||||
| void InvokeReleaser(Rank1, Releaser&& releaser, absl::string_view) { | ||||
|   ::absl::base_internal::invoke(std::forward<Releaser>(releaser)); | ||||
| } | ||||
| 
 | ||||
| // We use CompressedTuple so that we can benefit from EBCO.
 | ||||
| template <typename Releaser> | ||||
| struct CordRepExternalImpl | ||||
|     : public CordRepExternal, | ||||
|       public ::absl::container_internal::CompressedTuple<Releaser> { | ||||
|   // The extra int arg is so that we can avoid interfering with copy/move
 | ||||
|   // constructors while still benefitting from perfect forwarding.
 | ||||
|   template <typename T> | ||||
|   CordRepExternalImpl(T&& releaser, int) | ||||
|       : CordRepExternalImpl::CompressedTuple(std::forward<T>(releaser)) { | ||||
|     this->releaser_invoker = &Release; | ||||
|   } | ||||
| 
 | ||||
|   ~CordRepExternalImpl() { | ||||
|     InvokeReleaser(Rank0{}, std::move(this->template get<0>()), | ||||
|                    absl::string_view(base, length)); | ||||
|   } | ||||
| 
 | ||||
|   static void Release(CordRepExternal* rep) { | ||||
|     delete static_cast<CordRepExternalImpl*>(rep); | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| template <typename Str> | ||||
| struct ConstInitExternalStorage { | ||||
|   ABSL_CONST_INIT static CordRepExternal value; | ||||
| }; | ||||
| 
 | ||||
| template <typename Str> | ||||
| CordRepExternal ConstInitExternalStorage<Str>::value(Str::value); | ||||
| 
 | ||||
| enum { | ||||
|   kMaxInline = 15, | ||||
|   // Tag byte & kMaxInline means we are storing a pointer.
 | ||||
|   kTreeFlag = 1 << 4, | ||||
|   // Tag byte & kProfiledFlag means we are profiling the Cord.
 | ||||
|   kProfiledFlag = 1 << 5 | ||||
| }; | ||||
| 
 | ||||
| // If the data has length <= kMaxInline, we store it in `as_chars`, and
 | ||||
| // store the size in `tagged_size`.
 | ||||
| // Else we store it in a tree and store a pointer to that tree in
 | ||||
| // `as_tree.rep` and store a tag in `tagged_size`.
 | ||||
| struct AsTree { | ||||
|   absl::cord_internal::CordRep* rep; | ||||
|   char padding[kMaxInline + 1 - sizeof(absl::cord_internal::CordRep*) - 1]; | ||||
|   char tagged_size; | ||||
| }; | ||||
| 
 | ||||
| constexpr char GetOrNull(absl::string_view data, size_t pos) { | ||||
|   return pos < data.size() ? data[pos] : '\0'; | ||||
| } | ||||
| 
 | ||||
| union InlineData { | ||||
|   constexpr InlineData() : as_chars{} {} | ||||
|   explicit constexpr InlineData(AsTree tree) : as_tree(tree) {} | ||||
|   explicit constexpr InlineData(absl::string_view chars) | ||||
|       : as_chars{GetOrNull(chars, 0),  GetOrNull(chars, 1), | ||||
|                  GetOrNull(chars, 2),  GetOrNull(chars, 3), | ||||
|                  GetOrNull(chars, 4),  GetOrNull(chars, 5), | ||||
|                  GetOrNull(chars, 6),  GetOrNull(chars, 7), | ||||
|                  GetOrNull(chars, 8),  GetOrNull(chars, 9), | ||||
|                  GetOrNull(chars, 10), GetOrNull(chars, 11), | ||||
|                  GetOrNull(chars, 12), GetOrNull(chars, 13), | ||||
|                  GetOrNull(chars, 14), static_cast<char>(chars.size())} {} | ||||
| 
 | ||||
|   AsTree as_tree; | ||||
|   char as_chars[kMaxInline + 1]; | ||||
| }; | ||||
| static_assert(sizeof(InlineData) == kMaxInline + 1, ""); | ||||
| static_assert(sizeof(AsTree) == sizeof(InlineData), ""); | ||||
| static_assert(offsetof(AsTree, tagged_size) == kMaxInline, ""); | ||||
| 
 | ||||
| }  // namespace cord_internal
 | ||||
| ABSL_NAMESPACE_END | ||||
|  |  | |||
|  | @ -1,3 +1,17 @@ | |||
| // Copyright 2020 The Abseil Authors.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //      https://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| //
 | ||||
| // POSIX spec:
 | ||||
| //   http://pubs.opengroup.org/onlinepubs/009695399/functions/fprintf.html
 | ||||
|  |  | |||
|  | @ -1,3 +1,17 @@ | |||
| // Copyright 2020 The Abseil Authors.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //      https://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| #ifndef ABSL_STRINGS_INTERNAL_STR_FORMAT_ARG_H_ | ||||
| #define ABSL_STRINGS_INTERNAL_STR_FORMAT_ARG_H_ | ||||
| 
 | ||||
|  | @ -25,10 +39,12 @@ class Cord; | |||
| class FormatCountCapture; | ||||
| class FormatSink; | ||||
| 
 | ||||
| namespace str_format_internal { | ||||
| 
 | ||||
| template <absl::FormatConversionCharSet C> | ||||
| struct FormatConvertResult; | ||||
| class FormatConversionSpec; | ||||
| 
 | ||||
| namespace str_format_internal { | ||||
| 
 | ||||
| template <typename T, typename = void> | ||||
| struct HasUserDefinedConvert : std::false_type {}; | ||||
| 
 | ||||
|  | @ -39,6 +55,22 @@ struct HasUserDefinedConvert<T, void_t<decltype(AbslFormatConvert( | |||
|                                     std::declval<FormatSink*>()))>> | ||||
|     : std::true_type {}; | ||||
| 
 | ||||
| void AbslFormatConvert();  // Stops the lexical name lookup
 | ||||
| template <typename T> | ||||
| auto FormatConvertImpl(const T& v, FormatConversionSpecImpl conv, | ||||
|                        FormatSinkImpl* sink) | ||||
|     -> decltype(AbslFormatConvert(v, | ||||
|                                   std::declval<const FormatConversionSpec&>(), | ||||
|                                   std::declval<FormatSink*>())) { | ||||
|   using FormatConversionSpecT = | ||||
|       absl::enable_if_t<sizeof(const T& (*)()) != 0, FormatConversionSpec>; | ||||
|   using FormatSinkT = | ||||
|       absl::enable_if_t<sizeof(const T& (*)()) != 0, FormatSink>; | ||||
|   auto fcs = conv.Wrap<FormatConversionSpecT>(); | ||||
|   auto fs = sink->Wrap<FormatSinkT>(); | ||||
|   return AbslFormatConvert(v, fcs, &fs); | ||||
| } | ||||
| 
 | ||||
| template <typename T> | ||||
| class StreamedWrapper; | ||||
| 
 | ||||
|  | @ -46,6 +78,13 @@ class StreamedWrapper; | |||
| // then convert it, appending to `sink` and return `true`.
 | ||||
| // Otherwise fail and return `false`.
 | ||||
| 
 | ||||
| // AbslFormatConvert(v, conv, sink) is intended to be found by ADL on 'v'
 | ||||
| // as an extension mechanism. These FormatConvertImpl functions are the default
 | ||||
| // implementations.
 | ||||
| // The ADL search is augmented via the 'Sink*' parameter, which also
 | ||||
| // serves as a disambiguator to reject possible unintended 'AbslFormatConvert'
 | ||||
| // functions in the namespaces associated with 'v'.
 | ||||
| 
 | ||||
| // Raw pointers.
 | ||||
| struct VoidPtr { | ||||
|   VoidPtr() = default; | ||||
|  | @ -61,6 +100,11 @@ struct ArgConvertResult { | |||
|   bool value; | ||||
| }; | ||||
| 
 | ||||
| template <FormatConversionCharSet C> | ||||
| constexpr FormatConversionCharSet ExtractCharSet(FormatConvertResult<C>) { | ||||
|   return C; | ||||
| } | ||||
| 
 | ||||
| template <FormatConversionCharSet C> | ||||
| constexpr FormatConversionCharSet ExtractCharSet(ArgConvertResult<C>) { | ||||
|   return C; | ||||
|  |  | |||
|  | @ -6,6 +6,12 @@ | |||
| //
 | ||||
| //      https://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| #include "absl/strings/internal/str_format/arg.h" | ||||
| 
 | ||||
| #include <ostream> | ||||
|  | @ -23,8 +29,17 @@ class FormatArgImplTest : public ::testing::Test { | |||
|   enum Color { kRed, kGreen, kBlue }; | ||||
| 
 | ||||
|   static const char *hi() { return "hi"; } | ||||
| 
 | ||||
|   struct X {}; | ||||
| 
 | ||||
|   X x_; | ||||
| }; | ||||
| 
 | ||||
| inline FormatConvertResult<FormatConversionCharSet{}> AbslFormatConvert( | ||||
|     const FormatArgImplTest::X &, const FormatConversionSpec &, FormatSink *) { | ||||
|   return {false}; | ||||
| } | ||||
| 
 | ||||
| TEST_F(FormatArgImplTest, ToInt) { | ||||
|   int out = 0; | ||||
|   EXPECT_TRUE(FormatArgImplFriend::ToInt(FormatArgImpl(1), &out)); | ||||
|  | @ -59,6 +74,7 @@ TEST_F(FormatArgImplTest, ToInt) { | |||
|       FormatArgImpl(static_cast<int *>(nullptr)), &out)); | ||||
|   EXPECT_FALSE(FormatArgImplFriend::ToInt(FormatArgImpl(hi()), &out)); | ||||
|   EXPECT_FALSE(FormatArgImplFriend::ToInt(FormatArgImpl("hi"), &out)); | ||||
|   EXPECT_FALSE(FormatArgImplFriend::ToInt(FormatArgImpl(x_), &out)); | ||||
|   EXPECT_TRUE(FormatArgImplFriend::ToInt(FormatArgImpl(kBlue), &out)); | ||||
|   EXPECT_EQ(2, out); | ||||
| } | ||||
|  |  | |||
|  | @ -1,3 +1,17 @@ | |||
| // Copyright 2020 The Abseil Authors.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //      https://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| #include "absl/strings/internal/str_format/bind.h" | ||||
| 
 | ||||
| #include <cerrno> | ||||
|  | @ -221,7 +235,7 @@ int FprintF(std::FILE* output, const UntypedFormatSpecImpl format, | |||
|     errno = sink.error(); | ||||
|     return -1; | ||||
|   } | ||||
|   if (sink.count() > std::numeric_limits<int>::max()) { | ||||
|   if (sink.count() > static_cast<size_t>(std::numeric_limits<int>::max())) { | ||||
|     errno = EFBIG; | ||||
|     return -1; | ||||
|   } | ||||
|  |  | |||
|  | @ -1,3 +1,17 @@ | |||
| // Copyright 2020 The Abseil Authors.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //      https://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| #ifndef ABSL_STRINGS_INTERNAL_STR_FORMAT_BIND_H_ | ||||
| #define ABSL_STRINGS_INTERNAL_STR_FORMAT_BIND_H_ | ||||
| 
 | ||||
|  | @ -119,10 +133,11 @@ class FormatSpecTemplate | |||
| 
 | ||||
| #endif  // ABSL_INTERNAL_ENABLE_FORMAT_CHECKER
 | ||||
| 
 | ||||
|   template <FormatConversionCharSet... C, | ||||
|             typename = typename std::enable_if< | ||||
|                 AllOf(sizeof...(C) == sizeof...(Args), Contains(Args, | ||||
|                                                                 C)...)>::type> | ||||
|   template < | ||||
|       FormatConversionCharSet... C, | ||||
|       typename = typename std::enable_if<sizeof...(C) == sizeof...(Args)>::type, | ||||
|       typename = typename std::enable_if<AllOf(Contains(Args, | ||||
|                                                         C)...)>::type> | ||||
|   FormatSpecTemplate(const ExtendedParsedFormat<C...>& pc)  // NOLINT
 | ||||
|       : Base(&pc) {} | ||||
| }; | ||||
|  |  | |||
|  | @ -1,3 +1,17 @@ | |||
| // Copyright 2020 The Abseil Authors.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //      https://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| #include "absl/strings/internal/str_format/bind.h" | ||||
| 
 | ||||
| #include <string.h> | ||||
|  |  | |||
|  | @ -1,3 +1,17 @@ | |||
| // Copyright 2020 The Abseil Authors.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //      https://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| #ifndef ABSL_STRINGS_INTERNAL_STR_FORMAT_CHECKER_H_ | ||||
| #define ABSL_STRINGS_INTERNAL_STR_FORMAT_CHECKER_H_ | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,3 +1,17 @@ | |||
| // Copyright 2020 The Abseil Authors.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //      https://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| #include <string> | ||||
| 
 | ||||
| #include "gmock/gmock.h" | ||||
|  |  | |||
|  | @ -1,3 +1,17 @@ | |||
| // Copyright 2020 The Abseil Authors.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //      https://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| #include <errno.h> | ||||
| #include <stdarg.h> | ||||
| #include <stdio.h> | ||||
|  | @ -12,6 +26,7 @@ | |||
| #include "gtest/gtest.h" | ||||
| #include "absl/base/internal/raw_logging.h" | ||||
| #include "absl/strings/internal/str_format/bind.h" | ||||
| #include "absl/strings/match.h" | ||||
| #include "absl/types/optional.h" | ||||
| 
 | ||||
| namespace absl { | ||||
|  | @ -19,6 +34,13 @@ ABSL_NAMESPACE_BEGIN | |||
| namespace str_format_internal { | ||||
| namespace { | ||||
| 
 | ||||
| struct NativePrintfTraits { | ||||
|   bool hex_float_has_glibc_rounding; | ||||
|   bool hex_float_prefers_denormal_repr; | ||||
|   bool hex_float_uses_minimal_precision_when_not_specified; | ||||
|   bool hex_float_optimizes_leading_digit_bit_count; | ||||
| }; | ||||
| 
 | ||||
| template <typename T, size_t N> | ||||
| size_t ArraySize(T (&)[N]) { | ||||
|   return N; | ||||
|  | @ -118,6 +140,63 @@ std::string StrPrint(const char *format, ...) { | |||
|   return result; | ||||
| } | ||||
| 
 | ||||
| NativePrintfTraits VerifyNativeImplementationImpl() { | ||||
|   NativePrintfTraits result; | ||||
| 
 | ||||
|   // >>> hex_float_has_glibc_rounding. To have glibc's rounding behavior we need
 | ||||
|   // to meet three requirements:
 | ||||
|   //
 | ||||
|   //   - The threshold for rounding up is 8 (for e.g. MSVC uses 9).
 | ||||
|   //   - If the digits lower than than the 8 are non-zero then we round up.
 | ||||
|   //   - If the digits lower than the 8 are all zero then we round toward even.
 | ||||
|   //
 | ||||
|   // The numbers below represent all the cases covering {below,at,above} the
 | ||||
|   // threshold (8) with both {zero,non-zero} lower bits and both {even,odd}
 | ||||
|   // preceding digits.
 | ||||
|   const double d0079 = 65657.0;  // 0x1.0079p+16
 | ||||
|   const double d0179 = 65913.0;  // 0x1.0179p+16
 | ||||
|   const double d0080 = 65664.0;  // 0x1.0080p+16
 | ||||
|   const double d0180 = 65920.0;  // 0x1.0180p+16
 | ||||
|   const double d0081 = 65665.0;  // 0x1.0081p+16
 | ||||
|   const double d0181 = 65921.0;  // 0x1.0181p+16
 | ||||
|   result.hex_float_has_glibc_rounding = | ||||
|       StartsWith(StrPrint("%.2a", d0079), "0x1.00") && | ||||
|       StartsWith(StrPrint("%.2a", d0179), "0x1.01") && | ||||
|       StartsWith(StrPrint("%.2a", d0080), "0x1.00") && | ||||
|       StartsWith(StrPrint("%.2a", d0180), "0x1.02") && | ||||
|       StartsWith(StrPrint("%.2a", d0081), "0x1.01") && | ||||
|       StartsWith(StrPrint("%.2a", d0181), "0x1.02"); | ||||
| 
 | ||||
|   // >>> hex_float_prefers_denormal_repr. Formatting `denormal` on glibc yields
 | ||||
|   // "0x0.0000000000001p-1022", whereas on std libs that don't use denormal
 | ||||
|   // representation it would either be 0x1p-1074 or 0x1.0000000000000-1074.
 | ||||
|   const double denormal = std::numeric_limits<double>::denorm_min(); | ||||
|   result.hex_float_prefers_denormal_repr = | ||||
|       StartsWith(StrPrint("%a", denormal), "0x0.0000000000001"); | ||||
| 
 | ||||
|   // >>> hex_float_uses_minimal_precision_when_not_specified. Some (non-glibc)
 | ||||
|   // libs will format the following as "0x1.0079000000000p+16".
 | ||||
|   result.hex_float_uses_minimal_precision_when_not_specified = | ||||
|       (StrPrint("%a", d0079) == "0x1.0079p+16"); | ||||
| 
 | ||||
|   // >>> hex_float_optimizes_leading_digit_bit_count. The number 1.5, when
 | ||||
|   // formatted by glibc should yield "0x1.8p+0" for `double` and "0xcp-3" for
 | ||||
|   // `long double`, i.e., number of bits in the leading digit is adapted to the
 | ||||
|   // number of bits in the mantissa.
 | ||||
|   const double d_15 = 1.5; | ||||
|   const long double ld_15 = 1.5; | ||||
|   result.hex_float_optimizes_leading_digit_bit_count = | ||||
|       StartsWith(StrPrint("%a", d_15), "0x1.8") && | ||||
|       StartsWith(StrPrint("%La", ld_15), "0xc"); | ||||
| 
 | ||||
|   return result; | ||||
| } | ||||
| 
 | ||||
| const NativePrintfTraits &VerifyNativeImplementation() { | ||||
|   static NativePrintfTraits native_traits = VerifyNativeImplementationImpl(); | ||||
|   return native_traits; | ||||
| } | ||||
| 
 | ||||
| class FormatConvertTest : public ::testing::Test { }; | ||||
| 
 | ||||
| template <typename T> | ||||
|  | @ -474,6 +553,68 @@ TEST_F(FormatConvertTest, Uint128) { | |||
|   } | ||||
| } | ||||
| 
 | ||||
| template <typename Floating> | ||||
| void TestWithMultipleFormatsHelper(const std::vector<Floating> &floats) { | ||||
|   const NativePrintfTraits &native_traits = VerifyNativeImplementation(); | ||||
|   // Reserve the space to ensure we don't allocate memory in the output itself.
 | ||||
|   std::string str_format_result; | ||||
|   str_format_result.reserve(1 << 20); | ||||
|   std::string string_printf_result; | ||||
|   string_printf_result.reserve(1 << 20); | ||||
| 
 | ||||
|   const char *const kFormats[] = { | ||||
|       "%",  "%.3", "%8.5", "%500",   "%.5000", "%.60", "%.30",   "%03", | ||||
|       "%+", "% ",  "%-10", "%#15.3", "%#.0",   "%.0",  "%1$*2$", "%1$.*2$"}; | ||||
| 
 | ||||
|   for (const char *fmt : kFormats) { | ||||
|     for (char f : {'f', 'F',  //
 | ||||
|                    'g', 'G',  //
 | ||||
|                    'a', 'A',  //
 | ||||
|                    'e', 'E'}) { | ||||
|       std::string fmt_str = std::string(fmt) + f; | ||||
| 
 | ||||
|       if (fmt == absl::string_view("%.5000") && f != 'f' && f != 'F' && | ||||
|           f != 'a' && f != 'A') { | ||||
|         // This particular test takes way too long with snprintf.
 | ||||
|         // Disable for the case we are not implementing natively.
 | ||||
|         continue; | ||||
|       } | ||||
| 
 | ||||
|       if ((f == 'a' || f == 'A') && | ||||
|           !native_traits.hex_float_has_glibc_rounding) { | ||||
|         continue; | ||||
|       } | ||||
| 
 | ||||
|       for (Floating d : floats) { | ||||
|         if (!native_traits.hex_float_prefers_denormal_repr && | ||||
|             (f == 'a' || f == 'A') && std::fpclassify(d) == FP_SUBNORMAL) { | ||||
|           continue; | ||||
|         } | ||||
|         int i = -10; | ||||
|         FormatArgImpl args[2] = {FormatArgImpl(d), FormatArgImpl(i)}; | ||||
|         UntypedFormatSpecImpl format(fmt_str); | ||||
| 
 | ||||
|         string_printf_result.clear(); | ||||
|         StrAppend(&string_printf_result, fmt_str.c_str(), d, i); | ||||
|         str_format_result.clear(); | ||||
| 
 | ||||
|         { | ||||
|           AppendPack(&str_format_result, format, absl::MakeSpan(args)); | ||||
|         } | ||||
| 
 | ||||
|         if (string_printf_result != str_format_result) { | ||||
|           // 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(string_printf_result, str_format_result) | ||||
|               << fmt_str << " " << StrPrint("%.18g", d) << " " | ||||
|               << StrPrint("%a", d) << " " << StrPrint("%.50f", d); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| TEST_F(FormatConvertTest, Float) { | ||||
| #ifdef _MSC_VER | ||||
|   // MSVC has a different rounding policy than us so we can't test our
 | ||||
|  | @ -481,9 +622,62 @@ TEST_F(FormatConvertTest, Float) { | |||
|   return; | ||||
| #endif  // _MSC_VER
 | ||||
| 
 | ||||
|   const char *const kFormats[] = { | ||||
|       "%",  "%.3", "%8.5", "%500",   "%.5000", "%.60", "%.30",   "%03", | ||||
|       "%+", "% ",  "%-10", "%#15.3", "%#.0",   "%.0",  "%1$*2$", "%1$.*2$"}; | ||||
|   std::vector<float> floats = {0.0f, | ||||
|                                -0.0f, | ||||
|                                .9999999f, | ||||
|                                9999999.f, | ||||
|                                std::numeric_limits<float>::max(), | ||||
|                                -std::numeric_limits<float>::max(), | ||||
|                                std::numeric_limits<float>::min(), | ||||
|                                -std::numeric_limits<float>::min(), | ||||
|                                std::numeric_limits<float>::lowest(), | ||||
|                                -std::numeric_limits<float>::lowest(), | ||||
|                                std::numeric_limits<float>::epsilon(), | ||||
|                                std::numeric_limits<float>::epsilon() + 1.0f, | ||||
|                                std::numeric_limits<float>::infinity(), | ||||
|                                -std::numeric_limits<float>::infinity()}; | ||||
| 
 | ||||
|   // Some regression tests.
 | ||||
|   floats.push_back(0.999999989f); | ||||
| 
 | ||||
|   if (std::numeric_limits<float>::has_denorm != std::denorm_absent) { | ||||
|     floats.push_back(std::numeric_limits<float>::denorm_min()); | ||||
|     floats.push_back(-std::numeric_limits<float>::denorm_min()); | ||||
|   } | ||||
| 
 | ||||
|   for (float base : | ||||
|        {1.f, 12.f, 123.f, 1234.f, 12345.f, 123456.f, 1234567.f, 12345678.f, | ||||
|         123456789.f, 1234567890.f, 12345678901.f, 12345678.f, 12345678.f}) { | ||||
|     for (int exp = -123; exp <= 123; ++exp) { | ||||
|       for (int sign : {1, -1}) { | ||||
|         floats.push_back(sign * std::ldexp(base, exp)); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   for (int exp = -300; exp <= 300; ++exp) { | ||||
|     const float all_ones_mantissa = 0xffffff; | ||||
|     floats.push_back(std::ldexp(all_ones_mantissa, exp)); | ||||
|   } | ||||
| 
 | ||||
|   // Remove duplicates to speed up the logic below.
 | ||||
|   std::sort(floats.begin(), floats.end()); | ||||
|   floats.erase(std::unique(floats.begin(), floats.end()), floats.end()); | ||||
| 
 | ||||
| #ifndef __APPLE__ | ||||
|   // Apple formats NaN differently (+nan) vs. (nan)
 | ||||
|   floats.push_back(std::nan("")); | ||||
| #endif | ||||
| 
 | ||||
|   TestWithMultipleFormatsHelper(floats); | ||||
| } | ||||
| 
 | ||||
| TEST_F(FormatConvertTest, Double) { | ||||
| #ifdef _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
 | ||||
| 
 | ||||
|   std::vector<double> doubles = {0.0, | ||||
|                                  -0.0, | ||||
|  | @ -554,52 +748,10 @@ TEST_F(FormatConvertTest, Float) { | |||
|   doubles.push_back(std::nan("")); | ||||
| #endif | ||||
| 
 | ||||
|   // Reserve the space to ensure we don't allocate memory in the output itself.
 | ||||
|   std::string str_format_result; | ||||
|   str_format_result.reserve(1 << 20); | ||||
|   std::string string_printf_result; | ||||
|   string_printf_result.reserve(1 << 20); | ||||
| 
 | ||||
|   for (const char *fmt : kFormats) { | ||||
|     for (char f : {'f', 'F',  //
 | ||||
|                    'g', 'G',  //
 | ||||
|                    'a', 'A',  //
 | ||||
|                    'e', 'E'}) { | ||||
|       std::string fmt_str = std::string(fmt) + f; | ||||
| 
 | ||||
|       if (fmt == absl::string_view("%.5000") && f != 'f' && f != 'F') { | ||||
|         // This particular test takes way too long with snprintf.
 | ||||
|         // Disable for the case we are not implementing natively.
 | ||||
|         continue; | ||||
|       } | ||||
| 
 | ||||
|       for (double d : doubles) { | ||||
|         int i = -10; | ||||
|         FormatArgImpl args[2] = {FormatArgImpl(d), FormatArgImpl(i)}; | ||||
|         UntypedFormatSpecImpl format(fmt_str); | ||||
| 
 | ||||
|         string_printf_result.clear(); | ||||
|         StrAppend(&string_printf_result, fmt_str.c_str(), d, i); | ||||
|         str_format_result.clear(); | ||||
| 
 | ||||
|         { | ||||
|           AppendPack(&str_format_result, format, absl::MakeSpan(args)); | ||||
|         } | ||||
| 
 | ||||
|         if (string_printf_result != str_format_result) { | ||||
|           // 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(string_printf_result, str_format_result) | ||||
|               << fmt_str << " " << StrPrint("%.18g", d) << " " | ||||
|               << StrPrint("%a", d) << " " << StrPrint("%.1080f", d); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   TestWithMultipleFormatsHelper(doubles); | ||||
| } | ||||
| 
 | ||||
| TEST_F(FormatConvertTest, FloatRound) { | ||||
| TEST_F(FormatConvertTest, DoubleRound) { | ||||
|   std::string s; | ||||
|   const auto format = [&](const char *fmt, double d) -> std::string & { | ||||
|     s.clear(); | ||||
|  | @ -704,6 +856,193 @@ TEST_F(FormatConvertTest, FloatRound) { | |||
|             "1837869002408041296803276054561138153076171875"); | ||||
| } | ||||
| 
 | ||||
| TEST_F(FormatConvertTest, DoubleRoundA) { | ||||
|   const NativePrintfTraits &native_traits = VerifyNativeImplementation(); | ||||
|   std::string s; | ||||
|   const auto format = [&](const char *fmt, double d) -> std::string & { | ||||
|     s.clear(); | ||||
|     FormatArgImpl args[1] = {FormatArgImpl(d)}; | ||||
|     AppendPack(&s, UntypedFormatSpecImpl(fmt), absl::MakeSpan(args)); | ||||
|     if (native_traits.hex_float_has_glibc_rounding) { | ||||
|       EXPECT_EQ(StrPrint(fmt, d), s); | ||||
|     } | ||||
|     return s; | ||||
|   }; | ||||
| 
 | ||||
|   // 0x1.00018000p+100
 | ||||
|   const double on_boundary_odd = 1267679614447900152596896153600.0; | ||||
|   EXPECT_EQ(format("%.0a", on_boundary_odd), "0x1p+100"); | ||||
|   EXPECT_EQ(format("%.1a", on_boundary_odd), "0x1.0p+100"); | ||||
|   EXPECT_EQ(format("%.2a", on_boundary_odd), "0x1.00p+100"); | ||||
|   EXPECT_EQ(format("%.3a", on_boundary_odd), "0x1.000p+100"); | ||||
|   EXPECT_EQ(format("%.4a", on_boundary_odd), "0x1.0002p+100");  // round
 | ||||
|   EXPECT_EQ(format("%.5a", on_boundary_odd), "0x1.00018p+100"); | ||||
|   EXPECT_EQ(format("%.6a", on_boundary_odd), "0x1.000180p+100"); | ||||
| 
 | ||||
|   // 0x1.00028000p-2
 | ||||
|   const double on_boundary_even = 0.250009536743164062500; | ||||
|   EXPECT_EQ(format("%.0a", on_boundary_even), "0x1p-2"); | ||||
|   EXPECT_EQ(format("%.1a", on_boundary_even), "0x1.0p-2"); | ||||
|   EXPECT_EQ(format("%.2a", on_boundary_even), "0x1.00p-2"); | ||||
|   EXPECT_EQ(format("%.3a", on_boundary_even), "0x1.000p-2"); | ||||
|   EXPECT_EQ(format("%.4a", on_boundary_even), "0x1.0002p-2");  // no round
 | ||||
|   EXPECT_EQ(format("%.5a", on_boundary_even), "0x1.00028p-2"); | ||||
|   EXPECT_EQ(format("%.6a", on_boundary_even), "0x1.000280p-2"); | ||||
| 
 | ||||
|   // 0x1.00018001p+1
 | ||||
|   const double slightly_over = 2.00004577683284878730773925781250; | ||||
|   EXPECT_EQ(format("%.0a", slightly_over), "0x1p+1"); | ||||
|   EXPECT_EQ(format("%.1a", slightly_over), "0x1.0p+1"); | ||||
|   EXPECT_EQ(format("%.2a", slightly_over), "0x1.00p+1"); | ||||
|   EXPECT_EQ(format("%.3a", slightly_over), "0x1.000p+1"); | ||||
|   EXPECT_EQ(format("%.4a", slightly_over), "0x1.0002p+1"); | ||||
|   EXPECT_EQ(format("%.5a", slightly_over), "0x1.00018p+1"); | ||||
|   EXPECT_EQ(format("%.6a", slightly_over), "0x1.000180p+1"); | ||||
| 
 | ||||
|   // 0x1.00017fffp+0
 | ||||
|   const double slightly_under = 1.000022887950763106346130371093750; | ||||
|   EXPECT_EQ(format("%.0a", slightly_under), "0x1p+0"); | ||||
|   EXPECT_EQ(format("%.1a", slightly_under), "0x1.0p+0"); | ||||
|   EXPECT_EQ(format("%.2a", slightly_under), "0x1.00p+0"); | ||||
|   EXPECT_EQ(format("%.3a", slightly_under), "0x1.000p+0"); | ||||
|   EXPECT_EQ(format("%.4a", slightly_under), "0x1.0001p+0"); | ||||
|   EXPECT_EQ(format("%.5a", slightly_under), "0x1.00018p+0"); | ||||
|   EXPECT_EQ(format("%.6a", slightly_under), "0x1.000180p+0"); | ||||
|   EXPECT_EQ(format("%.7a", slightly_under), "0x1.0001800p+0"); | ||||
| 
 | ||||
|   // 0x1.1b3829ac28058p+3
 | ||||
|   const double hex_value = 8.85060580848964661981881363317370414733886718750; | ||||
|   EXPECT_EQ(format("%.0a", hex_value), "0x1p+3"); | ||||
|   EXPECT_EQ(format("%.1a", hex_value), "0x1.2p+3"); | ||||
|   EXPECT_EQ(format("%.2a", hex_value), "0x1.1bp+3"); | ||||
|   EXPECT_EQ(format("%.3a", hex_value), "0x1.1b4p+3"); | ||||
|   EXPECT_EQ(format("%.4a", hex_value), "0x1.1b38p+3"); | ||||
|   EXPECT_EQ(format("%.5a", hex_value), "0x1.1b383p+3"); | ||||
|   EXPECT_EQ(format("%.6a", hex_value), "0x1.1b382ap+3"); | ||||
|   EXPECT_EQ(format("%.7a", hex_value), "0x1.1b3829bp+3"); | ||||
|   EXPECT_EQ(format("%.8a", hex_value), "0x1.1b3829acp+3"); | ||||
|   EXPECT_EQ(format("%.9a", hex_value), "0x1.1b3829ac3p+3"); | ||||
|   EXPECT_EQ(format("%.10a", hex_value), "0x1.1b3829ac28p+3"); | ||||
|   EXPECT_EQ(format("%.11a", hex_value), "0x1.1b3829ac280p+3"); | ||||
|   EXPECT_EQ(format("%.12a", hex_value), "0x1.1b3829ac2806p+3"); | ||||
|   EXPECT_EQ(format("%.13a", hex_value), "0x1.1b3829ac28058p+3"); | ||||
|   EXPECT_EQ(format("%.14a", hex_value), "0x1.1b3829ac280580p+3"); | ||||
|   EXPECT_EQ(format("%.15a", hex_value), "0x1.1b3829ac2805800p+3"); | ||||
|   EXPECT_EQ(format("%.16a", hex_value), "0x1.1b3829ac28058000p+3"); | ||||
|   EXPECT_EQ(format("%.17a", hex_value), "0x1.1b3829ac280580000p+3"); | ||||
|   EXPECT_EQ(format("%.18a", hex_value), "0x1.1b3829ac2805800000p+3"); | ||||
|   EXPECT_EQ(format("%.19a", hex_value), "0x1.1b3829ac28058000000p+3"); | ||||
|   EXPECT_EQ(format("%.20a", hex_value), "0x1.1b3829ac280580000000p+3"); | ||||
|   EXPECT_EQ(format("%.21a", hex_value), "0x1.1b3829ac2805800000000p+3"); | ||||
| 
 | ||||
|   // 0x1.0818283848586p+3
 | ||||
|   const double hex_value2 = 8.2529488658208371987257123691961169242858886718750; | ||||
|   EXPECT_EQ(format("%.0a", hex_value2), "0x1p+3"); | ||||
|   EXPECT_EQ(format("%.1a", hex_value2), "0x1.1p+3"); | ||||
|   EXPECT_EQ(format("%.2a", hex_value2), "0x1.08p+3"); | ||||
|   EXPECT_EQ(format("%.3a", hex_value2), "0x1.082p+3"); | ||||
|   EXPECT_EQ(format("%.4a", hex_value2), "0x1.0818p+3"); | ||||
|   EXPECT_EQ(format("%.5a", hex_value2), "0x1.08183p+3"); | ||||
|   EXPECT_EQ(format("%.6a", hex_value2), "0x1.081828p+3"); | ||||
|   EXPECT_EQ(format("%.7a", hex_value2), "0x1.0818284p+3"); | ||||
|   EXPECT_EQ(format("%.8a", hex_value2), "0x1.08182838p+3"); | ||||
|   EXPECT_EQ(format("%.9a", hex_value2), "0x1.081828385p+3"); | ||||
|   EXPECT_EQ(format("%.10a", hex_value2), "0x1.0818283848p+3"); | ||||
|   EXPECT_EQ(format("%.11a", hex_value2), "0x1.08182838486p+3"); | ||||
|   EXPECT_EQ(format("%.12a", hex_value2), "0x1.081828384858p+3"); | ||||
|   EXPECT_EQ(format("%.13a", hex_value2), "0x1.0818283848586p+3"); | ||||
|   EXPECT_EQ(format("%.14a", hex_value2), "0x1.08182838485860p+3"); | ||||
|   EXPECT_EQ(format("%.15a", hex_value2), "0x1.081828384858600p+3"); | ||||
|   EXPECT_EQ(format("%.16a", hex_value2), "0x1.0818283848586000p+3"); | ||||
|   EXPECT_EQ(format("%.17a", hex_value2), "0x1.08182838485860000p+3"); | ||||
|   EXPECT_EQ(format("%.18a", hex_value2), "0x1.081828384858600000p+3"); | ||||
|   EXPECT_EQ(format("%.19a", hex_value2), "0x1.0818283848586000000p+3"); | ||||
|   EXPECT_EQ(format("%.20a", hex_value2), "0x1.08182838485860000000p+3"); | ||||
|   EXPECT_EQ(format("%.21a", hex_value2), "0x1.081828384858600000000p+3"); | ||||
| } | ||||
| 
 | ||||
| TEST_F(FormatConvertTest, LongDoubleRoundA) { | ||||
|   if (std::numeric_limits<long double>::digits % 4 != 0) { | ||||
|     // This test doesn't really make sense to run on platforms where a long
 | ||||
|     // double has a different mantissa size (mod 4) than Prod, since then the
 | ||||
|     // leading digit will be formatted differently.
 | ||||
|     return; | ||||
|   } | ||||
|   const NativePrintfTraits &native_traits = VerifyNativeImplementation(); | ||||
|   std::string s; | ||||
|   const auto format = [&](const char *fmt, long double d) -> std::string & { | ||||
|     s.clear(); | ||||
|     FormatArgImpl args[1] = {FormatArgImpl(d)}; | ||||
|     AppendPack(&s, UntypedFormatSpecImpl(fmt), absl::MakeSpan(args)); | ||||
|     if (native_traits.hex_float_has_glibc_rounding && | ||||
|         native_traits.hex_float_optimizes_leading_digit_bit_count) { | ||||
|       EXPECT_EQ(StrPrint(fmt, d), s); | ||||
|     } | ||||
|     return s; | ||||
|   }; | ||||
| 
 | ||||
|   // 0x8.8p+4
 | ||||
|   const long double on_boundary_even = 136.0; | ||||
|   EXPECT_EQ(format("%.0La", on_boundary_even), "0x8p+4"); | ||||
|   EXPECT_EQ(format("%.1La", on_boundary_even), "0x8.8p+4"); | ||||
|   EXPECT_EQ(format("%.2La", on_boundary_even), "0x8.80p+4"); | ||||
|   EXPECT_EQ(format("%.3La", on_boundary_even), "0x8.800p+4"); | ||||
|   EXPECT_EQ(format("%.4La", on_boundary_even), "0x8.8000p+4"); | ||||
|   EXPECT_EQ(format("%.5La", on_boundary_even), "0x8.80000p+4"); | ||||
|   EXPECT_EQ(format("%.6La", on_boundary_even), "0x8.800000p+4"); | ||||
| 
 | ||||
|   // 0x9.8p+4
 | ||||
|   const long double on_boundary_odd = 152.0; | ||||
|   EXPECT_EQ(format("%.0La", on_boundary_odd), "0xap+4"); | ||||
|   EXPECT_EQ(format("%.1La", on_boundary_odd), "0x9.8p+4"); | ||||
|   EXPECT_EQ(format("%.2La", on_boundary_odd), "0x9.80p+4"); | ||||
|   EXPECT_EQ(format("%.3La", on_boundary_odd), "0x9.800p+4"); | ||||
|   EXPECT_EQ(format("%.4La", on_boundary_odd), "0x9.8000p+4"); | ||||
|   EXPECT_EQ(format("%.5La", on_boundary_odd), "0x9.80000p+4"); | ||||
|   EXPECT_EQ(format("%.6La", on_boundary_odd), "0x9.800000p+4"); | ||||
| 
 | ||||
|   // 0x8.80001p+24
 | ||||
|   const long double slightly_over = 142606352.0; | ||||
|   EXPECT_EQ(format("%.0La", slightly_over), "0x9p+24"); | ||||
|   EXPECT_EQ(format("%.1La", slightly_over), "0x8.8p+24"); | ||||
|   EXPECT_EQ(format("%.2La", slightly_over), "0x8.80p+24"); | ||||
|   EXPECT_EQ(format("%.3La", slightly_over), "0x8.800p+24"); | ||||
|   EXPECT_EQ(format("%.4La", slightly_over), "0x8.8000p+24"); | ||||
|   EXPECT_EQ(format("%.5La", slightly_over), "0x8.80001p+24"); | ||||
|   EXPECT_EQ(format("%.6La", slightly_over), "0x8.800010p+24"); | ||||
| 
 | ||||
|   // 0x8.7ffffp+24
 | ||||
|   const long double slightly_under = 142606320.0; | ||||
|   EXPECT_EQ(format("%.0La", slightly_under), "0x8p+24"); | ||||
|   EXPECT_EQ(format("%.1La", slightly_under), "0x8.8p+24"); | ||||
|   EXPECT_EQ(format("%.2La", slightly_under), "0x8.80p+24"); | ||||
|   EXPECT_EQ(format("%.3La", slightly_under), "0x8.800p+24"); | ||||
|   EXPECT_EQ(format("%.4La", slightly_under), "0x8.8000p+24"); | ||||
|   EXPECT_EQ(format("%.5La", slightly_under), "0x8.7ffffp+24"); | ||||
|   EXPECT_EQ(format("%.6La", slightly_under), "0x8.7ffff0p+24"); | ||||
|   EXPECT_EQ(format("%.7La", slightly_under), "0x8.7ffff00p+24"); | ||||
| 
 | ||||
|   // 0xc.0828384858688000p+128
 | ||||
|   const long double eights = 4094231060438608800781871108094404067328.0; | ||||
|   EXPECT_EQ(format("%.0La", eights), "0xcp+128"); | ||||
|   EXPECT_EQ(format("%.1La", eights), "0xc.1p+128"); | ||||
|   EXPECT_EQ(format("%.2La", eights), "0xc.08p+128"); | ||||
|   EXPECT_EQ(format("%.3La", eights), "0xc.083p+128"); | ||||
|   EXPECT_EQ(format("%.4La", eights), "0xc.0828p+128"); | ||||
|   EXPECT_EQ(format("%.5La", eights), "0xc.08284p+128"); | ||||
|   EXPECT_EQ(format("%.6La", eights), "0xc.082838p+128"); | ||||
|   EXPECT_EQ(format("%.7La", eights), "0xc.0828385p+128"); | ||||
|   EXPECT_EQ(format("%.8La", eights), "0xc.08283848p+128"); | ||||
|   EXPECT_EQ(format("%.9La", eights), "0xc.082838486p+128"); | ||||
|   EXPECT_EQ(format("%.10La", eights), "0xc.0828384858p+128"); | ||||
|   EXPECT_EQ(format("%.11La", eights), "0xc.08283848587p+128"); | ||||
|   EXPECT_EQ(format("%.12La", eights), "0xc.082838485868p+128"); | ||||
|   EXPECT_EQ(format("%.13La", eights), "0xc.0828384858688p+128"); | ||||
|   EXPECT_EQ(format("%.14La", eights), "0xc.08283848586880p+128"); | ||||
|   EXPECT_EQ(format("%.15La", eights), "0xc.082838485868800p+128"); | ||||
|   EXPECT_EQ(format("%.16La", eights), "0xc.0828384858688000p+128"); | ||||
| } | ||||
| 
 | ||||
| // We don't actually store the results. This is just to exercise the rest of the
 | ||||
| // machinery.
 | ||||
| struct NullSink { | ||||
|  | @ -735,6 +1074,7 @@ TEST_F(FormatConvertTest, LongDouble) { | |||
|   // implementation against the native one there.
 | ||||
|   return; | ||||
| #endif  // _MSC_VER
 | ||||
|   const NativePrintfTraits &native_traits = VerifyNativeImplementation(); | ||||
|   const char *const kFormats[] = {"%",    "%.3", "%8.5", "%9",  "%.5000", | ||||
|                                   "%.60", "%+",  "% ",   "%-10"}; | ||||
| 
 | ||||
|  | @ -777,12 +1117,20 @@ TEST_F(FormatConvertTest, LongDouble) { | |||
|                    'e', 'E'}) { | ||||
|       std::string fmt_str = std::string(fmt) + 'L' + f; | ||||
| 
 | ||||
|       if (fmt == absl::string_view("%.5000") && f != 'f' && f != 'F') { | ||||
|       if (fmt == absl::string_view("%.5000") && f != 'f' && f != 'F' && | ||||
|           f != 'a' && f != 'A') { | ||||
|         // This particular test takes way too long with snprintf.
 | ||||
|         // Disable for the case we are not implementing natively.
 | ||||
|         continue; | ||||
|       } | ||||
| 
 | ||||
|       if (f == 'a' || f == 'A') { | ||||
|         if (!native_traits.hex_float_has_glibc_rounding || | ||||
|             !native_traits.hex_float_optimizes_leading_digit_bit_count) { | ||||
|           continue; | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       for (auto d : doubles) { | ||||
|         FormatArgImpl arg(d); | ||||
|         UntypedFormatSpecImpl format(fmt_str); | ||||
|  | @ -797,7 +1145,8 @@ TEST_F(FormatConvertTest, LongDouble) { | |||
|   } | ||||
| } | ||||
| 
 | ||||
| TEST_F(FormatConvertTest, IntAsFloat) { | ||||
| TEST_F(FormatConvertTest, IntAsDouble) { | ||||
|   const NativePrintfTraits &native_traits = VerifyNativeImplementation(); | ||||
|   const int kMin = std::numeric_limits<int>::min(); | ||||
|   const int kMax = std::numeric_limits<int>::max(); | ||||
|   const int ia[] = { | ||||
|  | @ -813,14 +1162,17 @@ TEST_F(FormatConvertTest, IntAsFloat) { | |||
|       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" }, | ||||
|     std::vector<Expectation> expect = { | ||||
|         {__LINE__, StrPrint("%f", dx), "%f"}, | ||||
|         {__LINE__, StrPrint("%12f", dx), "%12f"}, | ||||
|         {__LINE__, StrPrint("%.12f", dx), "%.12f"}, | ||||
|         {__LINE__, StrPrint("%.12a", dx), "%.12a"}, | ||||
|     }; | ||||
|     for (const Expectation &e : kExpect) { | ||||
|     if (native_traits.hex_float_uses_minimal_precision_when_not_specified) { | ||||
|       Expectation ex = {__LINE__, StrPrint("%12a", dx), "%12a"}; | ||||
|       expect.push_back(ex); | ||||
|     } | ||||
|     for (const Expectation &e : expect) { | ||||
|       SCOPED_TRACE(e.line); | ||||
|       SCOPED_TRACE(e.fmt); | ||||
|       UntypedFormatSpecImpl format(e.fmt); | ||||
|  | @ -865,6 +1217,25 @@ TEST_F(FormatConvertTest, ExpectedFailures) { | |||
|   EXPECT_TRUE(FormatFails("%*d", "")); | ||||
| } | ||||
| 
 | ||||
| // Sanity check to make sure that we are testing what we think we're testing on
 | ||||
| // e.g. the x86_64+glibc platform.
 | ||||
| TEST_F(FormatConvertTest, GlibcHasCorrectTraits) { | ||||
| #if !defined(__GLIBC__) || !defined(__x86_64__) | ||||
|   return; | ||||
| #endif | ||||
|   const NativePrintfTraits &native_traits = VerifyNativeImplementation(); | ||||
|   // If one of the following tests break then it is either because the above PP
 | ||||
|   // macro guards failed to exclude a new platform (likely) or because something
 | ||||
|   // has changed in the implemention of glibc sprintf float formatting behavior.
 | ||||
|   // If the latter, then the code that computes these flags needs to be
 | ||||
|   // revisited and/or possibly the StrFormat implementation.
 | ||||
|   EXPECT_TRUE(native_traits.hex_float_has_glibc_rounding); | ||||
|   EXPECT_TRUE(native_traits.hex_float_prefers_denormal_repr); | ||||
|   EXPECT_TRUE( | ||||
|       native_traits.hex_float_uses_minimal_precision_when_not_specified); | ||||
|   EXPECT_TRUE(native_traits.hex_float_optimizes_leading_digit_bit_count); | ||||
| } | ||||
| 
 | ||||
| }  // namespace
 | ||||
| }  // namespace str_format_internal
 | ||||
| ABSL_NAMESPACE_END | ||||
|  |  | |||
|  | @ -33,6 +33,29 @@ std::string Flags::ToString() const { | |||
|   return s; | ||||
| } | ||||
| 
 | ||||
| #define ABSL_INTERNAL_X_VAL(id) \ | ||||
|   constexpr absl::FormatConversionChar FormatConversionCharInternal::id; | ||||
| ABSL_INTERNAL_CONVERSION_CHARS_EXPAND_(ABSL_INTERNAL_X_VAL, ) | ||||
| #undef ABSL_INTERNAL_X_VAL | ||||
| // NOLINTNEXTLINE(readability-redundant-declaration)
 | ||||
| constexpr absl::FormatConversionChar FormatConversionCharInternal::kNone; | ||||
| 
 | ||||
| #define ABSL_INTERNAL_CHAR_SET_CASE(c) \ | ||||
|   constexpr FormatConversionCharSet FormatConversionCharSetInternal::c; | ||||
| ABSL_INTERNAL_CONVERSION_CHARS_EXPAND_(ABSL_INTERNAL_CHAR_SET_CASE, ) | ||||
| #undef ABSL_INTERNAL_CHAR_SET_CASE | ||||
| 
 | ||||
| // NOLINTNEXTLINE(readability-redundant-declaration)
 | ||||
| constexpr FormatConversionCharSet FormatConversionCharSetInternal::kStar; | ||||
| // NOLINTNEXTLINE(readability-redundant-declaration)
 | ||||
| constexpr FormatConversionCharSet FormatConversionCharSetInternal::kIntegral; | ||||
| // NOLINTNEXTLINE(readability-redundant-declaration)
 | ||||
| constexpr FormatConversionCharSet FormatConversionCharSetInternal::kFloating; | ||||
| // NOLINTNEXTLINE(readability-redundant-declaration)
 | ||||
| constexpr FormatConversionCharSet FormatConversionCharSetInternal::kNumeric; | ||||
| // NOLINTNEXTLINE(readability-redundant-declaration)
 | ||||
| constexpr FormatConversionCharSet FormatConversionCharSetInternal::kPointer; | ||||
| 
 | ||||
| bool FormatSinkImpl::PutPaddedString(string_view value, int width, | ||||
|                                      int precision, bool left) { | ||||
|   size_t space_remaining = 0; | ||||
|  |  | |||
|  | @ -31,11 +31,11 @@ | |||
| namespace absl { | ||||
| ABSL_NAMESPACE_BEGIN | ||||
| 
 | ||||
| namespace str_format_internal { | ||||
| 
 | ||||
| enum class FormatConversionChar : uint8_t; | ||||
| enum class FormatConversionCharSet : uint64_t; | ||||
| 
 | ||||
| namespace str_format_internal { | ||||
| 
 | ||||
| class FormatRawSinkImpl { | ||||
|  public: | ||||
|   // Implicitly convert from any type that provides the hook function as
 | ||||
|  | @ -361,14 +361,12 @@ struct FormatConversionCharSetInternal { | |||
|   static constexpr FormatConversionCharSet kStar = | ||||
|       FormatConversionCharToConvValue('*'); | ||||
| 
 | ||||
|   // Some predefined values (TODO(matthewbr), delete any that are unused).
 | ||||
|   static constexpr FormatConversionCharSet kIntegral = | ||||
|       FormatConversionCharSetUnion(d, i, u, o, x, X); | ||||
|   static constexpr FormatConversionCharSet kFloating = | ||||
|       FormatConversionCharSetUnion(a, e, f, g, A, E, F, G); | ||||
|   static constexpr FormatConversionCharSet kNumeric = | ||||
|       FormatConversionCharSetUnion(kIntegral, kFloating); | ||||
|   static constexpr FormatConversionCharSet kString = s; | ||||
|   static constexpr FormatConversionCharSet kPointer = p; | ||||
| }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -80,4 +80,19 @@ TEST(FormatExtensionTest, SinkAppendChars) { | |||
|     EXPECT_EQ(actual, expected); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| TEST(FormatExtensionTest, VerifyEnumEquality) { | ||||
| #define X_VAL(id)                           \ | ||||
|   EXPECT_EQ(absl::FormatConversionChar::id, \ | ||||
|             absl::str_format_internal::FormatConversionCharInternal::id); | ||||
|   ABSL_INTERNAL_CONVERSION_CHARS_EXPAND_(X_VAL, ); | ||||
| #undef X_VAL | ||||
| 
 | ||||
| #define X_VAL(id)                              \ | ||||
|   EXPECT_EQ(absl::FormatConversionCharSet::id, \ | ||||
|             absl::str_format_internal::FormatConversionCharSetInternal::id); | ||||
|   ABSL_INTERNAL_CONVERSION_CHARS_EXPAND_(X_VAL, ); | ||||
| #undef X_VAL | ||||
| } | ||||
| 
 | ||||
| }  // namespace
 | ||||
|  |  | |||
|  | @ -1,3 +1,17 @@ | |||
| // Copyright 2020 The Abseil Authors.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //      https://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| #include "absl/strings/internal/str_format/float_conversion.h" | ||||
| 
 | ||||
| #include <string.h> | ||||
|  | @ -15,6 +29,7 @@ | |||
| #include "absl/functional/function_ref.h" | ||||
| #include "absl/meta/type_traits.h" | ||||
| #include "absl/numeric/int128.h" | ||||
| #include "absl/strings/numbers.h" | ||||
| #include "absl/types/optional.h" | ||||
| #include "absl/types/span.h" | ||||
| 
 | ||||
|  | @ -119,7 +134,7 @@ class BinaryToDecimal { | |||
|     assert(exp > 0); | ||||
|     assert(exp <= std::numeric_limits<long double>::max_exponent); | ||||
|     static_assert( | ||||
|         StackArray::kMaxCapacity >= | ||||
|         static_cast<int>(StackArray::kMaxCapacity) >= | ||||
|             ChunksNeeded(std::numeric_limits<long double>::max_exponent), | ||||
|         ""); | ||||
| 
 | ||||
|  | @ -204,7 +219,7 @@ class BinaryToDecimal { | |||
|   } | ||||
| 
 | ||||
|  private: | ||||
|   static constexpr size_t kDigitsPerChunk = 9; | ||||
|   static constexpr int kDigitsPerChunk = 9; | ||||
| 
 | ||||
|   int decimal_start_; | ||||
|   int decimal_end_; | ||||
|  | @ -441,8 +456,10 @@ struct Padding { | |||
| }; | ||||
| 
 | ||||
| Padding ExtraWidthToPadding(size_t total_size, const FormatState &state) { | ||||
|   if (state.conv.width() < 0 || state.conv.width() <= total_size) | ||||
|   if (state.conv.width() < 0 || | ||||
|       static_cast<size_t>(state.conv.width()) <= total_size) { | ||||
|     return {0, 0, 0}; | ||||
|   } | ||||
|   int missing_chars = state.conv.width() - total_size; | ||||
|   if (state.conv.has_left_flag()) { | ||||
|     return {0, 0, missing_chars}; | ||||
|  | @ -453,26 +470,31 @@ Padding ExtraWidthToPadding(size_t total_size, const FormatState &state) { | |||
|   } | ||||
| } | ||||
| 
 | ||||
| void FinalPrint(absl::string_view data, int trailing_zeros, | ||||
|                 const FormatState &state) { | ||||
| void FinalPrint(const FormatState &state, absl::string_view data, | ||||
|                 int padding_offset, int trailing_zeros, | ||||
|                 absl::string_view data_postfix) { | ||||
|   if (state.conv.width() < 0) { | ||||
|     // No width specified. Fast-path.
 | ||||
|     if (state.sign_char != '\0') state.sink->Append(1, state.sign_char); | ||||
|     state.sink->Append(data); | ||||
|     state.sink->Append(trailing_zeros, '0'); | ||||
|     state.sink->Append(data_postfix); | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   auto padding = | ||||
|       ExtraWidthToPadding((state.sign_char != '\0' ? 1 : 0) + data.size() + | ||||
|                               static_cast<size_t>(trailing_zeros), | ||||
|                           state); | ||||
|   auto padding = ExtraWidthToPadding((state.sign_char != '\0' ? 1 : 0) + | ||||
|                                          data.size() + data_postfix.size() + | ||||
|                                          static_cast<size_t>(trailing_zeros), | ||||
|                                      state); | ||||
| 
 | ||||
|   state.sink->Append(padding.left_spaces, ' '); | ||||
|   if (state.sign_char != '\0') state.sink->Append(1, state.sign_char); | ||||
|   // Padding in general needs to be inserted somewhere in the middle of `data`.
 | ||||
|   state.sink->Append(data.substr(0, padding_offset)); | ||||
|   state.sink->Append(padding.zeros, '0'); | ||||
|   state.sink->Append(data); | ||||
|   state.sink->Append(data.substr(padding_offset)); | ||||
|   state.sink->Append(trailing_zeros, '0'); | ||||
|   state.sink->Append(data_postfix); | ||||
|   state.sink->Append(padding.right_spaces, ' '); | ||||
| } | ||||
| 
 | ||||
|  | @ -525,10 +547,11 @@ void FormatFFast(Int v, int exp, const FormatState &state) { | |||
|   // In `alt` mode (flag #) we keep the `.` even if there are no fractional
 | ||||
|   // digits. In non-alt mode, we strip it.
 | ||||
|   if (!state.ShouldPrintDot()) --size; | ||||
|   FinalPrint(absl::string_view(integral_digits_start, size), | ||||
|   FinalPrint(state, absl::string_view(integral_digits_start, size), | ||||
|              /*padding_offset=*/0, | ||||
|              static_cast<int>(state.precision - (fractional_digits_end - | ||||
|                                                  fractional_digits_start)), | ||||
|              state); | ||||
|              /*data_postfix=*/""); | ||||
| } | ||||
| 
 | ||||
| // Slow %f formatter for when the shifted value does not fit in a uint128, and
 | ||||
|  | @ -655,6 +678,255 @@ void FormatF(Int mantissa, int exp, const FormatState &state) { | |||
|   return FormatFFast(mantissa, exp, state); | ||||
| } | ||||
| 
 | ||||
| // Grab the group of four bits (nibble) from `n`. E.g., nibble 1 corresponds to
 | ||||
| // bits 4-7.
 | ||||
| template <typename Int> | ||||
| uint8_t GetNibble(Int n, int nibble_index) { | ||||
|   constexpr Int mask_low_nibble = Int{0xf}; | ||||
|   int shift = nibble_index * 4; | ||||
|   n &= mask_low_nibble << shift; | ||||
|   return static_cast<uint8_t>((n >> shift) & 0xf); | ||||
| } | ||||
| 
 | ||||
| // Add one to the given nibble, applying carry to higher nibbles. Returns true
 | ||||
| // if overflow, false otherwise.
 | ||||
| template <typename Int> | ||||
| bool IncrementNibble(int nibble_index, Int *n) { | ||||
|   constexpr int kShift = sizeof(Int) * 8 - 1; | ||||
|   constexpr int kNumNibbles = sizeof(Int) * 8 / 4; | ||||
|   Int before = *n >> kShift; | ||||
|   // Here we essentially want to take the number 1 and move it into the requsted
 | ||||
|   // nibble, then add it to *n to effectively increment the nibble. However,
 | ||||
|   // ASan will complain if we try to shift the 1 beyond the limits of the Int,
 | ||||
|   // i.e., if the nibble_index is out of range. So therefore we check for this
 | ||||
|   // and if we are out of range we just add 0 which leaves *n unchanged, which
 | ||||
|   // seems like the reasonable thing to do in that case.
 | ||||
|   *n += ((nibble_index >= kNumNibbles) ? 0 : (Int{1} << (nibble_index * 4))); | ||||
|   Int after = *n >> kShift; | ||||
|   return (before && !after) || (nibble_index >= kNumNibbles); | ||||
| } | ||||
| 
 | ||||
| // Return a mask with 1's in the given nibble and all lower nibbles.
 | ||||
| template <typename Int> | ||||
| Int MaskUpToNibbleInclusive(int nibble_index) { | ||||
|   constexpr int kNumNibbles = sizeof(Int) * 8 / 4; | ||||
|   static const Int ones = ~Int{0}; | ||||
|   return ones >> std::max(0, 4 * (kNumNibbles - nibble_index - 1)); | ||||
| } | ||||
| 
 | ||||
| // Return a mask with 1's below the given nibble.
 | ||||
| template <typename Int> | ||||
| Int MaskUpToNibbleExclusive(int nibble_index) { | ||||
|   return nibble_index <= 0 ? 0 : MaskUpToNibbleInclusive<Int>(nibble_index - 1); | ||||
| } | ||||
| 
 | ||||
| template <typename Int> | ||||
| Int MoveToNibble(uint8_t nibble, int nibble_index) { | ||||
|   return Int{nibble} << (4 * nibble_index); | ||||
| } | ||||
| 
 | ||||
| // Given mantissa size, find optimal # of mantissa bits to put in initial digit.
 | ||||
| //
 | ||||
| // In the hex representation we keep a single hex digit to the left of the dot.
 | ||||
| // However, the question as to how many bits of the mantissa should be put into
 | ||||
| // that hex digit in theory is arbitrary, but in practice it is optimal to
 | ||||
| // choose based on the size of the mantissa. E.g., for a `double`, there are 53
 | ||||
| // mantissa bits, so that means that we should put 1 bit to the left of the dot,
 | ||||
| // thereby leaving 52 bits to the right, which is evenly divisible by four and
 | ||||
| // thus all fractional digits represent actual precision. For a `long double`,
 | ||||
| // on the other hand, there are 64 bits of mantissa, thus we can use all four
 | ||||
| // bits for the initial hex digit and still have a number left over (60) that is
 | ||||
| // a multiple of four. Once again, the goal is to have all fractional digits
 | ||||
| // represent real precision.
 | ||||
| template <typename Float> | ||||
| constexpr int HexFloatLeadingDigitSizeInBits() { | ||||
|   return std::numeric_limits<Float>::digits % 4 > 0 | ||||
|              ? std::numeric_limits<Float>::digits % 4 | ||||
|              : 4; | ||||
| } | ||||
| 
 | ||||
| // This function captures the rounding behavior of glibc for hex float
 | ||||
| // representations. E.g. when rounding 0x1.ab800000 to a precision of .2
 | ||||
| // ("%.2a") glibc will round up because it rounds toward the even number (since
 | ||||
| // 0xb is an odd number, it will round up to 0xc). However, when rounding at a
 | ||||
| // point that is not followed by 800000..., it disregards the parity and rounds
 | ||||
| // up if > 8 and rounds down if < 8.
 | ||||
| template <typename Int> | ||||
| bool HexFloatNeedsRoundUp(Int mantissa, int final_nibble_displayed, | ||||
|                           uint8_t leading) { | ||||
|   // If the last nibble (hex digit) to be displayed is the lowest on in the
 | ||||
|   // mantissa then that means that we don't have any further nibbles to inform
 | ||||
|   // rounding, so don't round.
 | ||||
|   if (final_nibble_displayed <= 0) { | ||||
|     return false; | ||||
|   } | ||||
|   int rounding_nibble_idx = final_nibble_displayed - 1; | ||||
|   constexpr int kTotalNibbles = sizeof(Int) * 8 / 4; | ||||
|   assert(final_nibble_displayed <= kTotalNibbles); | ||||
|   Int mantissa_up_to_rounding_nibble_inclusive = | ||||
|       mantissa & MaskUpToNibbleInclusive<Int>(rounding_nibble_idx); | ||||
|   Int eight = MoveToNibble<Int>(8, rounding_nibble_idx); | ||||
|   if (mantissa_up_to_rounding_nibble_inclusive != eight) { | ||||
|     return mantissa_up_to_rounding_nibble_inclusive > eight; | ||||
|   } | ||||
|   // Nibble in question == 8.
 | ||||
|   uint8_t round_if_odd = (final_nibble_displayed == kTotalNibbles) | ||||
|                              ? leading | ||||
|                              : GetNibble(mantissa, final_nibble_displayed); | ||||
|   return round_if_odd % 2 == 1; | ||||
| } | ||||
| 
 | ||||
| // Stores values associated with a Float type needed by the FormatA
 | ||||
| // implementation in order to avoid templatizing that function by the Float
 | ||||
| // type.
 | ||||
| struct HexFloatTypeParams { | ||||
|   template <typename Float> | ||||
|   explicit HexFloatTypeParams(Float) | ||||
|       : min_exponent(std::numeric_limits<Float>::min_exponent - 1), | ||||
|         leading_digit_size_bits(HexFloatLeadingDigitSizeInBits<Float>()) { | ||||
|     assert(leading_digit_size_bits >= 1 && leading_digit_size_bits <= 4); | ||||
|   } | ||||
| 
 | ||||
|   int min_exponent; | ||||
|   int leading_digit_size_bits; | ||||
| }; | ||||
| 
 | ||||
| // Hex Float Rounding. First check if we need to round; if so, then we do that
 | ||||
| // by manipulating (incrementing) the mantissa, that way we can later print the
 | ||||
| // mantissa digits by iterating through them in the same way regardless of
 | ||||
| // whether a rounding happened.
 | ||||
| template <typename Int> | ||||
| void FormatARound(bool precision_specified, const FormatState &state, | ||||
|                   uint8_t *leading, Int *mantissa, int *exp) { | ||||
|   constexpr int kTotalNibbles = sizeof(Int) * 8 / 4; | ||||
|   // Index of the last nibble that we could display given precision.
 | ||||
|   int final_nibble_displayed = | ||||
|       precision_specified ? std::max(0, (kTotalNibbles - state.precision)) : 0; | ||||
|   if (HexFloatNeedsRoundUp(*mantissa, final_nibble_displayed, *leading)) { | ||||
|     // Need to round up.
 | ||||
|     bool overflow = IncrementNibble(final_nibble_displayed, mantissa); | ||||
|     *leading += (overflow ? 1 : 0); | ||||
|     if (ABSL_PREDICT_FALSE(*leading > 15)) { | ||||
|       // We have overflowed the leading digit. This would mean that we would
 | ||||
|       // need two hex digits to the left of the dot, which is not allowed. So
 | ||||
|       // adjust the mantissa and exponent so that the result is always 1.0eXXX.
 | ||||
|       *leading = 1; | ||||
|       *mantissa = 0; | ||||
|       *exp += 4; | ||||
|     } | ||||
|   } | ||||
|   // Now that we have handled a possible round-up we can go ahead and zero out
 | ||||
|   // all the nibbles of the mantissa that we won't need.
 | ||||
|   if (precision_specified) { | ||||
|     *mantissa &= ~MaskUpToNibbleExclusive<Int>(final_nibble_displayed); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| template <typename Int> | ||||
| void FormatANormalize(const HexFloatTypeParams float_traits, uint8_t *leading, | ||||
|                       Int *mantissa, int *exp) { | ||||
|   constexpr int kIntBits = sizeof(Int) * 8; | ||||
|   static const Int kHighIntBit = Int{1} << (kIntBits - 1); | ||||
|   const int kLeadDigitBitsCount = float_traits.leading_digit_size_bits; | ||||
|   // Normalize mantissa so that highest bit set is in MSB position, unless we
 | ||||
|   // get interrupted by the exponent threshold.
 | ||||
|   while (*mantissa && !(*mantissa & kHighIntBit)) { | ||||
|     if (ABSL_PREDICT_FALSE(*exp - 1 < float_traits.min_exponent)) { | ||||
|       *mantissa >>= (float_traits.min_exponent - *exp); | ||||
|       *exp = float_traits.min_exponent; | ||||
|       return; | ||||
|     } | ||||
|     *mantissa <<= 1; | ||||
|     --*exp; | ||||
|   } | ||||
|   // Extract bits for leading digit then shift them away leaving the
 | ||||
|   // fractional part.
 | ||||
|   *leading = | ||||
|       static_cast<uint8_t>(*mantissa >> (kIntBits - kLeadDigitBitsCount)); | ||||
|   *exp -= (*mantissa != 0) ? kLeadDigitBitsCount : *exp; | ||||
|   *mantissa <<= kLeadDigitBitsCount; | ||||
| } | ||||
| 
 | ||||
| template <typename Int> | ||||
| void FormatA(const HexFloatTypeParams float_traits, Int mantissa, int exp, | ||||
|              bool uppercase, const FormatState &state) { | ||||
|   // Int properties.
 | ||||
|   constexpr int kIntBits = sizeof(Int) * 8; | ||||
|   constexpr int kTotalNibbles = sizeof(Int) * 8 / 4; | ||||
|   // Did the user specify a precision explicitly?
 | ||||
|   const bool precision_specified = state.conv.precision() >= 0; | ||||
| 
 | ||||
|   // ========== Normalize/Denormalize ==========
 | ||||
|   exp += kIntBits;  // make all digits fractional digits.
 | ||||
|   // This holds the (up to four) bits of leading digit, i.e., the '1' in the
 | ||||
|   // number 0x1.e6fp+2. It's always > 0 unless number is zero or denormal.
 | ||||
|   uint8_t leading = 0; | ||||
|   FormatANormalize(float_traits, &leading, &mantissa, &exp); | ||||
| 
 | ||||
|   // =============== Rounding ==================
 | ||||
|   // Check if we need to round; if so, then we do that by manipulating
 | ||||
|   // (incrementing) the mantissa before beginning to print characters.
 | ||||
|   FormatARound(precision_specified, state, &leading, &mantissa, &exp); | ||||
| 
 | ||||
|   // ============= Format Result ===============
 | ||||
|   // This buffer holds the "0x1.ab1de3" portion of "0x1.ab1de3pe+2". Compute the
 | ||||
|   // size with long double which is the largest of the floats.
 | ||||
|   constexpr size_t kBufSizeForHexFloatRepr = | ||||
|       2                                               // 0x
 | ||||
|       + std::numeric_limits<long double>::digits / 4  // number of hex digits
 | ||||
|       + 1                                             // round up
 | ||||
|       + 1;                                            // "." (dot)
 | ||||
|   char digits_buffer[kBufSizeForHexFloatRepr]; | ||||
|   char *digits_iter = digits_buffer; | ||||
|   const char *const digits = | ||||
|       static_cast<const char *>("0123456789ABCDEF0123456789abcdef") + | ||||
|       (uppercase ? 0 : 16); | ||||
| 
 | ||||
|   // =============== Hex Prefix ================
 | ||||
|   *digits_iter++ = '0'; | ||||
|   *digits_iter++ = uppercase ? 'X' : 'x'; | ||||
| 
 | ||||
|   // ========== Non-Fractional Digit ===========
 | ||||
|   *digits_iter++ = digits[leading]; | ||||
| 
 | ||||
|   // ================== Dot ====================
 | ||||
|   // There are three reasons we might need a dot. Keep in mind that, at this
 | ||||
|   // point, the mantissa holds only the fractional part.
 | ||||
|   if ((precision_specified && state.precision > 0) || | ||||
|       (!precision_specified && mantissa > 0) || state.conv.has_alt_flag()) { | ||||
|     *digits_iter++ = '.'; | ||||
|   } | ||||
| 
 | ||||
|   // ============ Fractional Digits ============
 | ||||
|   int digits_emitted = 0; | ||||
|   while (mantissa > 0) { | ||||
|     *digits_iter++ = digits[GetNibble(mantissa, kTotalNibbles - 1)]; | ||||
|     mantissa <<= 4; | ||||
|     ++digits_emitted; | ||||
|   } | ||||
|   int trailing_zeros = | ||||
|       precision_specified ? state.precision - digits_emitted : 0; | ||||
|   assert(trailing_zeros >= 0); | ||||
|   auto digits_result = string_view(digits_buffer, digits_iter - digits_buffer); | ||||
| 
 | ||||
|   // =============== Exponent ==================
 | ||||
|   constexpr size_t kBufSizeForExpDecRepr = | ||||
|       numbers_internal::kFastToBufferSize  // requred for FastIntToBuffer
 | ||||
|       + 1                                  // 'p' or 'P'
 | ||||
|       + 1;                                 // '+' or '-'
 | ||||
|   char exp_buffer[kBufSizeForExpDecRepr]; | ||||
|   exp_buffer[0] = uppercase ? 'P' : 'p'; | ||||
|   exp_buffer[1] = exp >= 0 ? '+' : '-'; | ||||
|   numbers_internal::FastIntToBuffer(exp < 0 ? -exp : exp, exp_buffer + 2); | ||||
| 
 | ||||
|   // ============ Assemble Result ==============
 | ||||
|   FinalPrint(state,           //
 | ||||
|              digits_result,   // 0xN.NNN...
 | ||||
|              2,               // offset in `data` to start padding if needed.
 | ||||
|              trailing_zeros,  // num remaining mantissa padding zeros
 | ||||
|              exp_buffer);     // exponent
 | ||||
| } | ||||
| 
 | ||||
| char *CopyStringTo(absl::string_view v, char *out) { | ||||
|   std::memcpy(out, v.data(), v.size()); | ||||
|   return out + v.size(); | ||||
|  | @ -1103,7 +1375,10 @@ bool FloatToSink(const Float v, const FormatConversionSpecImpl &conv, | |||
|     } | ||||
|   } else if (c == FormatConversionCharInternal::a || | ||||
|              c == FormatConversionCharInternal::A) { | ||||
|     return FallbackToSnprintf(v, conv, sink); | ||||
|     bool uppercase = (c == FormatConversionCharInternal::A); | ||||
|     FormatA(HexFloatTypeParams(Float{}), decomposed.mantissa, | ||||
|             decomposed.exponent, uppercase, {sign_char, precision, conv, sink}); | ||||
|     return true; | ||||
|   } else { | ||||
|     return false; | ||||
|   } | ||||
|  | @ -1131,7 +1406,7 @@ bool ConvertFloatImpl(long double v, const FormatConversionSpecImpl &conv, | |||
| 
 | ||||
| bool ConvertFloatImpl(float v, const FormatConversionSpecImpl &conv, | ||||
|                       FormatSinkImpl *sink) { | ||||
|   return FloatToSink(v, conv, sink); | ||||
|   return FloatToSink(static_cast<double>(v), conv, sink); | ||||
| } | ||||
| 
 | ||||
| bool ConvertFloatImpl(double v, const FormatConversionSpecImpl &conv, | ||||
|  |  | |||
|  | @ -1,3 +1,17 @@ | |||
| // Copyright 2020 The Abseil Authors.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //      https://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| #ifndef ABSL_STRINGS_INTERNAL_STR_FORMAT_FLOAT_CONVERSION_H_ | ||||
| #define ABSL_STRINGS_INTERNAL_STR_FORMAT_FLOAT_CONVERSION_H_ | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,3 +1,17 @@ | |||
| // Copyright 2020 The Abseil Authors.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //      https://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| #include "absl/strings/internal/str_format/parser.h" | ||||
| 
 | ||||
| #include <assert.h> | ||||
|  |  | |||
|  | @ -1,3 +1,17 @@ | |||
| // Copyright 2020 The Abseil Authors.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //      https://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| #ifndef ABSL_STRINGS_INTERNAL_STR_FORMAT_PARSER_H_ | ||||
| #define ABSL_STRINGS_INTERNAL_STR_FORMAT_PARSER_H_ | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,3 +1,17 @@ | |||
| // Copyright 2020 The Abseil Authors.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //      https://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| #include "absl/strings/internal/str_format/parser.h" | ||||
| 
 | ||||
| #include <string.h> | ||||
|  |  | |||
|  | @ -51,9 +51,9 @@ ABSL_NAMESPACE_BEGIN | |||
| namespace strings_internal { | ||||
| 
 | ||||
| // This class is implicitly constructible from everything that absl::string_view
 | ||||
| // is implicitly constructible from. If it's constructed from a temporary
 | ||||
| // string, the data is moved into a data member so its lifetime matches that of
 | ||||
| // the ConvertibleToStringView instance.
 | ||||
| // is implicitly constructible from, except for rvalue strings.  This means it
 | ||||
| // can be used as a function parameter in places where passing a temporary
 | ||||
| // string might cause memory lifetime issues.
 | ||||
| class ConvertibleToStringView { | ||||
|  public: | ||||
|   ConvertibleToStringView(const char* s)  // NOLINT(runtime/explicit)
 | ||||
|  | @ -65,41 +65,12 @@ class ConvertibleToStringView { | |||
|       : value_(s) {} | ||||
| 
 | ||||
|   // Matches rvalue strings and moves their data to a member.
 | ||||
|   ConvertibleToStringView(std::string&& s)  // NOLINT(runtime/explicit)
 | ||||
|       : copy_(std::move(s)), value_(copy_) {} | ||||
| 
 | ||||
|   ConvertibleToStringView(const ConvertibleToStringView& other) | ||||
|       : copy_(other.copy_), | ||||
|         value_(other.IsSelfReferential() ? copy_ : other.value_) {} | ||||
| 
 | ||||
|   ConvertibleToStringView(ConvertibleToStringView&& other) { | ||||
|     StealMembers(std::move(other)); | ||||
|   } | ||||
| 
 | ||||
|   ConvertibleToStringView& operator=(ConvertibleToStringView other) { | ||||
|     StealMembers(std::move(other)); | ||||
|     return *this; | ||||
|   } | ||||
|   ConvertibleToStringView(std::string&& s) = delete; | ||||
|   ConvertibleToStringView(const std::string&& s) = delete; | ||||
| 
 | ||||
|   absl::string_view value() const { return value_; } | ||||
| 
 | ||||
|  private: | ||||
|   // Returns true if ctsp's value refers to its internal copy_ member.
 | ||||
|   bool IsSelfReferential() const { return value_.data() == copy_.data(); } | ||||
| 
 | ||||
|   void StealMembers(ConvertibleToStringView&& other) { | ||||
|     if (other.IsSelfReferential()) { | ||||
|       copy_ = std::move(other.copy_); | ||||
|       value_ = copy_; | ||||
|       other.value_ = other.copy_; | ||||
|     } else { | ||||
|       value_ = other.value_; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   // Holds the data moved from temporary std::string arguments. Declared first
 | ||||
|   // so that 'value' can refer to 'copy_'.
 | ||||
|   std::string copy_; | ||||
|   absl::string_view value_; | ||||
| }; | ||||
| 
 | ||||
|  | @ -273,7 +244,11 @@ struct SplitterIsConvertibleTo | |||
| // the split strings: only strings for which the predicate returns true will be
 | ||||
| // kept. A Predicate object is any unary functor that takes an absl::string_view
 | ||||
| // and returns bool.
 | ||||
| template <typename Delimiter, typename Predicate> | ||||
| //
 | ||||
| // The StringType parameter can be either string_view or string, depending on
 | ||||
| // whether the Splitter refers to a string stored elsewhere, or if the string
 | ||||
| // resides inside the Splitter itself.
 | ||||
| template <typename Delimiter, typename Predicate, typename StringType> | ||||
| class Splitter { | ||||
|  public: | ||||
|   using DelimiterType = Delimiter; | ||||
|  | @ -281,12 +256,12 @@ class Splitter { | |||
|   using const_iterator = strings_internal::SplitIterator<Splitter>; | ||||
|   using value_type = typename std::iterator_traits<const_iterator>::value_type; | ||||
| 
 | ||||
|   Splitter(ConvertibleToStringView input_text, Delimiter d, Predicate p) | ||||
|   Splitter(StringType input_text, Delimiter d, Predicate p) | ||||
|       : text_(std::move(input_text)), | ||||
|         delimiter_(std::move(d)), | ||||
|         predicate_(std::move(p)) {} | ||||
| 
 | ||||
|   absl::string_view text() const { return text_.value(); } | ||||
|   absl::string_view text() const { return text_; } | ||||
|   const Delimiter& delimiter() const { return delimiter_; } | ||||
|   const Predicate& predicate() const { return predicate_; } | ||||
| 
 | ||||
|  | @ -336,7 +311,7 @@ class Splitter { | |||
|     Container operator()(const Splitter& splitter) const { | ||||
|       Container c; | ||||
|       auto it = std::inserter(c, c.end()); | ||||
|       for (const auto sp : splitter) { | ||||
|       for (const auto& sp : splitter) { | ||||
|         *it++ = ValueType(sp); | ||||
|       } | ||||
|       return c; | ||||
|  | @ -401,7 +376,7 @@ class Splitter { | |||
|       Container m; | ||||
|       typename Container::iterator it; | ||||
|       bool insert = true; | ||||
|       for (const auto sp : splitter) { | ||||
|       for (const auto& sp : splitter) { | ||||
|         if (insert) { | ||||
|           it = Inserter<Container>::Insert(&m, First(sp), Second()); | ||||
|         } else { | ||||
|  | @ -443,7 +418,7 @@ class Splitter { | |||
|     }; | ||||
|   }; | ||||
| 
 | ||||
|   ConvertibleToStringView text_; | ||||
|   StringType text_; | ||||
|   Delimiter delimiter_; | ||||
|   Predicate predicate_; | ||||
| }; | ||||
|  |  | |||
							
								
								
									
										70
									
								
								third_party/abseil_cpp/absl/strings/internal/string_constant.h
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								third_party/abseil_cpp/absl/strings/internal/string_constant.h
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,70 @@ | |||
| // Copyright 2020 The Abseil Authors.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //      https://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| #ifndef ABSL_STRINGS_INTERNAL_STRING_CONSTANT_H_ | ||||
| #define ABSL_STRINGS_INTERNAL_STRING_CONSTANT_H_ | ||||
| 
 | ||||
| #include "absl/meta/type_traits.h" | ||||
| #include "absl/strings/string_view.h" | ||||
| 
 | ||||
| namespace absl { | ||||
| ABSL_NAMESPACE_BEGIN | ||||
| namespace strings_internal { | ||||
| 
 | ||||
| // StringConstant<T> represents a compile time string constant.
 | ||||
| // It can be accessed via its `absl::string_view value` static member.
 | ||||
| // It is guaranteed that the `string_view` returned has constant `.data()`,
 | ||||
| // constant `.size()` and constant `value[i]` for all `0 <= i < .size()`
 | ||||
| //
 | ||||
| // The `T` is an opaque type. It is guaranteed that different string constants
 | ||||
| // will have different values of `T`. This allows users to associate the string
 | ||||
| // constant with other static state at compile time.
 | ||||
| //
 | ||||
| // Instances should be made using the `MakeStringConstant()` factory function
 | ||||
| // below.
 | ||||
| template <typename T> | ||||
| struct StringConstant { | ||||
|  private: | ||||
|   // Returns true if `view` points to constant data.
 | ||||
|   // Otherwise, it can't be constant evaluated.
 | ||||
|   static constexpr bool ValidateConstant(absl::string_view view) { | ||||
|     return view.empty() || 2 * view[0] != 1; | ||||
|   } | ||||
| 
 | ||||
|  public: | ||||
|   static constexpr absl::string_view value = T{}(); | ||||
|   constexpr absl::string_view operator()() const { return value; } | ||||
| 
 | ||||
|   static_assert(ValidateConstant(value), | ||||
|                 "The input string_view must point to constant data."); | ||||
| }; | ||||
| 
 | ||||
| template <typename T> | ||||
| constexpr absl::string_view StringConstant<T>::value;  // NOLINT
 | ||||
| 
 | ||||
| // Factory function for `StringConstant` instances.
 | ||||
| // It supports callables that have a constexpr default constructor and a
 | ||||
| // constexpr operator().
 | ||||
| // It must return an `absl::string_view` or `const char*` pointing to constant
 | ||||
| // data. This is validated at compile time.
 | ||||
| template <typename T> | ||||
| constexpr StringConstant<T> MakeStringConstant(T) { | ||||
|   return {}; | ||||
| } | ||||
| 
 | ||||
| }  // namespace strings_internal
 | ||||
| ABSL_NAMESPACE_END | ||||
| }  // namespace absl
 | ||||
| 
 | ||||
| #endif  // ABSL_STRINGS_INTERNAL_STRING_CONSTANT_H_
 | ||||
							
								
								
									
										60
									
								
								third_party/abseil_cpp/absl/strings/internal/string_constant_test.cc
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								third_party/abseil_cpp/absl/strings/internal/string_constant_test.cc
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,60 @@ | |||
| // Copyright 2020 The Abseil Authors.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //      https://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| #include "absl/strings/internal/string_constant.h" | ||||
| 
 | ||||
| #include "absl/meta/type_traits.h" | ||||
| #include "gmock/gmock.h" | ||||
| #include "gtest/gtest.h" | ||||
| 
 | ||||
| namespace { | ||||
| 
 | ||||
| using absl::strings_internal::MakeStringConstant; | ||||
| 
 | ||||
| struct Callable { | ||||
|   constexpr absl::string_view operator()() const { | ||||
|     return absl::string_view("Callable", 8); | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| TEST(StringConstant, Traits) { | ||||
|   constexpr auto str = MakeStringConstant(Callable{}); | ||||
|   using T = decltype(str); | ||||
| 
 | ||||
|   EXPECT_TRUE(std::is_empty<T>::value); | ||||
|   EXPECT_TRUE(std::is_trivial<T>::value); | ||||
|   EXPECT_TRUE(absl::is_trivially_default_constructible<T>::value); | ||||
|   EXPECT_TRUE(absl::is_trivially_copy_constructible<T>::value); | ||||
|   EXPECT_TRUE(absl::is_trivially_move_constructible<T>::value); | ||||
|   EXPECT_TRUE(absl::is_trivially_destructible<T>::value); | ||||
| } | ||||
| 
 | ||||
| TEST(StringConstant, MakeFromCallable) { | ||||
|   constexpr auto str = MakeStringConstant(Callable{}); | ||||
|   using T = decltype(str); | ||||
|   EXPECT_EQ(Callable{}(), T::value); | ||||
|   EXPECT_EQ(Callable{}(), str()); | ||||
| } | ||||
| 
 | ||||
| TEST(StringConstant, MakeFromStringConstant) { | ||||
|   // We want to make sure the StringConstant itself is a valid input to the
 | ||||
|   // factory function.
 | ||||
|   constexpr auto str = MakeStringConstant(Callable{}); | ||||
|   constexpr auto str2 = MakeStringConstant(str); | ||||
|   using T = decltype(str2); | ||||
|   EXPECT_EQ(Callable{}(), T::value); | ||||
|   EXPECT_EQ(Callable{}(), str2()); | ||||
| } | ||||
| 
 | ||||
| }  // namespace
 | ||||
							
								
								
									
										120
									
								
								third_party/abseil_cpp/absl/strings/numbers.cc
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										120
									
								
								third_party/abseil_cpp/absl/strings/numbers.cc
									
										
									
									
										vendored
									
									
								
							|  | @ -736,9 +736,18 @@ struct LookupTables { | |||
|         X / 35, X / 36,                                                   \ | ||||
|   } | ||||
| 
 | ||||
| // This kVmaxOverBase is generated with
 | ||||
| //  for (int base = 2; base < 37; ++base) {
 | ||||
| //    absl::uint128 max = std::numeric_limits<absl::uint128>::max();
 | ||||
| //    auto result = max / base;
 | ||||
| //    std::cout << "    MakeUint128(" << absl::Uint128High64(result) << "u, "
 | ||||
| //              << absl::Uint128Low64(result) << "u),\n";
 | ||||
| //  }
 | ||||
| // See https://godbolt.org/z/aneYsb
 | ||||
| //
 | ||||
| // uint128& operator/=(uint128) is not constexpr, so hardcode the resulting
 | ||||
| // array to avoid a static initializer.
 | ||||
| template <> | ||||
| template<> | ||||
| const uint128 LookupTables<uint128>::kVmaxOverBase[] = { | ||||
|     0, | ||||
|     0, | ||||
|  | @ -779,6 +788,111 @@ const uint128 LookupTables<uint128>::kVmaxOverBase[] = { | |||
|     MakeUint128(512409557603043100u, 8198552921648689607u), | ||||
| }; | ||||
| 
 | ||||
| // This kVmaxOverBase generated with
 | ||||
| //   for (int base = 2; base < 37; ++base) {
 | ||||
| //    absl::int128 max = std::numeric_limits<absl::int128>::max();
 | ||||
| //    auto result = max / base;
 | ||||
| //    std::cout << "\tMakeInt128(" << absl::Int128High64(result) << ", "
 | ||||
| //              << absl::Int128Low64(result) << "u),\n";
 | ||||
| //  }
 | ||||
| // See https://godbolt.org/z/7djYWz
 | ||||
| //
 | ||||
| // int128& operator/=(int128) is not constexpr, so hardcode the resulting array
 | ||||
| // to avoid a static initializer.
 | ||||
| template<> | ||||
| const int128 LookupTables<int128>::kVmaxOverBase[] = { | ||||
|     0, | ||||
|     0, | ||||
|     MakeInt128(4611686018427387903, 18446744073709551615u), | ||||
|     MakeInt128(3074457345618258602, 12297829382473034410u), | ||||
|     MakeInt128(2305843009213693951, 18446744073709551615u), | ||||
|     MakeInt128(1844674407370955161, 11068046444225730969u), | ||||
|     MakeInt128(1537228672809129301, 6148914691236517205u), | ||||
|     MakeInt128(1317624576693539401, 2635249153387078802u), | ||||
|     MakeInt128(1152921504606846975, 18446744073709551615u), | ||||
|     MakeInt128(1024819115206086200, 16397105843297379214u), | ||||
|     MakeInt128(922337203685477580, 14757395258967641292u), | ||||
|     MakeInt128(838488366986797800, 13415813871788764811u), | ||||
|     MakeInt128(768614336404564650, 12297829382473034410u), | ||||
|     MakeInt128(709490156681136600, 11351842506898185609u), | ||||
|     MakeInt128(658812288346769700, 10540996613548315209u), | ||||
|     MakeInt128(614891469123651720, 9838263505978427528u), | ||||
|     MakeInt128(576460752303423487, 18446744073709551615u), | ||||
|     MakeInt128(542551296285575047, 9765923333140350855u), | ||||
|     MakeInt128(512409557603043100, 8198552921648689607u), | ||||
|     MakeInt128(485440633518672410, 17475862806672206794u), | ||||
|     MakeInt128(461168601842738790, 7378697629483820646u), | ||||
|     MakeInt128(439208192231179800, 7027331075698876806u), | ||||
|     MakeInt128(419244183493398900, 6707906935894382405u), | ||||
|     MakeInt128(401016175515425035, 2406097053092550210u), | ||||
|     MakeInt128(384307168202282325, 6148914691236517205u), | ||||
|     MakeInt128(368934881474191032, 5902958103587056517u), | ||||
|     MakeInt128(354745078340568300, 5675921253449092804u), | ||||
|     MakeInt128(341606371735362066, 17763531330238827482u), | ||||
|     MakeInt128(329406144173384850, 5270498306774157604u), | ||||
|     MakeInt128(318047311615681924, 7633135478776366185u), | ||||
|     MakeInt128(307445734561825860, 4919131752989213764u), | ||||
|     MakeInt128(297528130221121800, 4760450083537948804u), | ||||
|     MakeInt128(288230376151711743, 18446744073709551615u), | ||||
|     MakeInt128(279496122328932600, 4471937957262921603u), | ||||
|     MakeInt128(271275648142787523, 14106333703424951235u), | ||||
|     MakeInt128(263524915338707880, 4216398645419326083u), | ||||
|     MakeInt128(256204778801521550, 4099276460824344803u), | ||||
| }; | ||||
| 
 | ||||
| // This kVminOverBase generated with
 | ||||
| //  for (int base = 2; base < 37; ++base) {
 | ||||
| //    absl::int128 min = std::numeric_limits<absl::int128>::min();
 | ||||
| //    auto result = min / base;
 | ||||
| //    std::cout << "\tMakeInt128(" << absl::Int128High64(result) << ", "
 | ||||
| //              << absl::Int128Low64(result) << "u),\n";
 | ||||
| //  }
 | ||||
| //
 | ||||
| // See https://godbolt.org/z/7djYWz
 | ||||
| //
 | ||||
| // int128& operator/=(int128) is not constexpr, so hardcode the resulting array
 | ||||
| // to avoid a static initializer.
 | ||||
| template<> | ||||
| const int128 LookupTables<int128>::kVminOverBase[] = { | ||||
|     0, | ||||
|     0, | ||||
|     MakeInt128(-4611686018427387904, 0u), | ||||
|     MakeInt128(-3074457345618258603, 6148914691236517206u), | ||||
|     MakeInt128(-2305843009213693952, 0u), | ||||
|     MakeInt128(-1844674407370955162, 7378697629483820647u), | ||||
|     MakeInt128(-1537228672809129302, 12297829382473034411u), | ||||
|     MakeInt128(-1317624576693539402, 15811494920322472814u), | ||||
|     MakeInt128(-1152921504606846976, 0u), | ||||
|     MakeInt128(-1024819115206086201, 2049638230412172402u), | ||||
|     MakeInt128(-922337203685477581, 3689348814741910324u), | ||||
|     MakeInt128(-838488366986797801, 5030930201920786805u), | ||||
|     MakeInt128(-768614336404564651, 6148914691236517206u), | ||||
|     MakeInt128(-709490156681136601, 7094901566811366007u), | ||||
|     MakeInt128(-658812288346769701, 7905747460161236407u), | ||||
|     MakeInt128(-614891469123651721, 8608480567731124088u), | ||||
|     MakeInt128(-576460752303423488, 0u), | ||||
|     MakeInt128(-542551296285575048, 8680820740569200761u), | ||||
|     MakeInt128(-512409557603043101, 10248191152060862009u), | ||||
|     MakeInt128(-485440633518672411, 970881267037344822u), | ||||
|     MakeInt128(-461168601842738791, 11068046444225730970u), | ||||
|     MakeInt128(-439208192231179801, 11419412998010674810u), | ||||
|     MakeInt128(-419244183493398901, 11738837137815169211u), | ||||
|     MakeInt128(-401016175515425036, 16040647020617001406u), | ||||
|     MakeInt128(-384307168202282326, 12297829382473034411u), | ||||
|     MakeInt128(-368934881474191033, 12543785970122495099u), | ||||
|     MakeInt128(-354745078340568301, 12770822820260458812u), | ||||
|     MakeInt128(-341606371735362067, 683212743470724134u), | ||||
|     MakeInt128(-329406144173384851, 13176245766935394012u), | ||||
|     MakeInt128(-318047311615681925, 10813608594933185431u), | ||||
|     MakeInt128(-307445734561825861, 13527612320720337852u), | ||||
|     MakeInt128(-297528130221121801, 13686293990171602812u), | ||||
|     MakeInt128(-288230376151711744, 0u), | ||||
|     MakeInt128(-279496122328932601, 13974806116446630013u), | ||||
|     MakeInt128(-271275648142787524, 4340410370284600381u), | ||||
|     MakeInt128(-263524915338707881, 14230345428290225533u), | ||||
|     MakeInt128(-256204778801521551, 14347467612885206813u), | ||||
| }; | ||||
| 
 | ||||
| template <typename IntType> | ||||
| const IntType LookupTables<IntType>::kVmaxOverBase[] = | ||||
|     X_OVER_BASE_INITIALIZER(std::numeric_limits<IntType>::max()); | ||||
|  | @ -948,6 +1062,10 @@ bool safe_strto64_base(absl::string_view text, int64_t* value, int base) { | |||
|   return safe_int_internal<int64_t>(text, value, base); | ||||
| } | ||||
| 
 | ||||
| bool safe_strto128_base(absl::string_view text, int128* value, int base) { | ||||
|   return safe_int_internal<absl::int128>(text, value, base); | ||||
| } | ||||
| 
 | ||||
| bool safe_strtou32_base(absl::string_view text, uint32_t* value, int base) { | ||||
|   return safe_uint_internal<uint32_t>(text, value, base); | ||||
| } | ||||
|  |  | |||
|  | @ -127,6 +127,8 @@ inline void PutTwoDigits(size_t i, char* buf) { | |||
| // safe_strto?() functions for implementing SimpleAtoi()
 | ||||
| bool safe_strto32_base(absl::string_view text, int32_t* value, int base); | ||||
| bool safe_strto64_base(absl::string_view text, int64_t* value, int base); | ||||
| bool safe_strto128_base(absl::string_view text, absl::int128* value, | ||||
|                          int base); | ||||
| bool safe_strtou32_base(absl::string_view text, uint32_t* value, int base); | ||||
| bool safe_strtou64_base(absl::string_view text, uint64_t* value, int base); | ||||
| bool safe_strtou128_base(absl::string_view text, absl::uint128* value, | ||||
|  | @ -255,6 +257,11 @@ ABSL_MUST_USE_RESULT bool SimpleAtoi(absl::string_view str, int_type* out) { | |||
|   return numbers_internal::safe_strtoi_base(str, out, 10); | ||||
| } | ||||
| 
 | ||||
| ABSL_MUST_USE_RESULT inline bool SimpleAtoi(absl::string_view str, | ||||
|                                             absl::int128* out) { | ||||
|   return numbers_internal::safe_strto128_base(str, out, 10); | ||||
| } | ||||
| 
 | ||||
| ABSL_MUST_USE_RESULT inline bool SimpleAtoi(absl::string_view str, | ||||
|                                             absl::uint128* out) { | ||||
|   return numbers_internal::safe_strtou128_base(str, out, 10); | ||||
|  |  | |||
|  | @ -251,7 +251,7 @@ TEST(Numbers, TestFastPrints) { | |||
| template <typename int_type, typename in_val_type> | ||||
| void VerifySimpleAtoiGood(in_val_type in_value, int_type exp_value) { | ||||
|   std::string s; | ||||
|   // uint128 can be streamed but not StrCat'd
 | ||||
|   // (u)int128 can be streamed but not StrCat'd.
 | ||||
|   absl::strings_internal::OStringStream(&s) << in_value; | ||||
|   int_type x = static_cast<int_type>(~exp_value); | ||||
|   EXPECT_TRUE(SimpleAtoi(s, &x)) | ||||
|  | @ -264,7 +264,9 @@ void VerifySimpleAtoiGood(in_val_type in_value, int_type exp_value) { | |||
| 
 | ||||
| template <typename int_type, typename in_val_type> | ||||
| void VerifySimpleAtoiBad(in_val_type in_value) { | ||||
|   std::string s = absl::StrCat(in_value); | ||||
|   std::string s; | ||||
|   // (u)int128 can be streamed but not StrCat'd.
 | ||||
|   absl::strings_internal::OStringStream(&s) << in_value; | ||||
|   int_type x; | ||||
|   EXPECT_FALSE(SimpleAtoi(s, &x)); | ||||
|   EXPECT_FALSE(SimpleAtoi(s.c_str(), &x)); | ||||
|  | @ -347,6 +349,31 @@ TEST(NumbersTest, Atoi) { | |||
|       std::numeric_limits<absl::uint128>::max(), | ||||
|       std::numeric_limits<absl::uint128>::max()); | ||||
| 
 | ||||
|   // SimpleAtoi(absl::string_view, absl::int128)
 | ||||
|   VerifySimpleAtoiGood<absl::int128>(0, 0); | ||||
|   VerifySimpleAtoiGood<absl::int128>(42, 42); | ||||
|   VerifySimpleAtoiGood<absl::int128>(-42, -42); | ||||
| 
 | ||||
|   VerifySimpleAtoiGood<absl::int128>(std::numeric_limits<int32_t>::min(), | ||||
|                                       std::numeric_limits<int32_t>::min()); | ||||
|   VerifySimpleAtoiGood<absl::int128>(std::numeric_limits<int32_t>::max(), | ||||
|                                       std::numeric_limits<int32_t>::max()); | ||||
|   VerifySimpleAtoiGood<absl::int128>(std::numeric_limits<uint32_t>::max(), | ||||
|                                       std::numeric_limits<uint32_t>::max()); | ||||
|   VerifySimpleAtoiGood<absl::int128>(std::numeric_limits<int64_t>::min(), | ||||
|                                       std::numeric_limits<int64_t>::min()); | ||||
|   VerifySimpleAtoiGood<absl::int128>(std::numeric_limits<int64_t>::max(), | ||||
|                                       std::numeric_limits<int64_t>::max()); | ||||
|   VerifySimpleAtoiGood<absl::int128>(std::numeric_limits<uint64_t>::max(), | ||||
|                                       std::numeric_limits<uint64_t>::max()); | ||||
|   VerifySimpleAtoiGood<absl::int128>( | ||||
|       std::numeric_limits<absl::int128>::min(), | ||||
|       std::numeric_limits<absl::int128>::min()); | ||||
|   VerifySimpleAtoiGood<absl::int128>( | ||||
|       std::numeric_limits<absl::int128>::max(), | ||||
|       std::numeric_limits<absl::int128>::max()); | ||||
|   VerifySimpleAtoiBad<absl::int128>(std::numeric_limits<absl::uint128>::max()); | ||||
| 
 | ||||
|   // Some other types
 | ||||
|   VerifySimpleAtoiGood<int>(-42, -42); | ||||
|   VerifySimpleAtoiGood<int32_t>(-42, -42); | ||||
|  | @ -359,6 +386,12 @@ TEST(NumbersTest, Atoi) { | |||
|   VerifySimpleAtoiGood<std::string::size_type>(42, 42); | ||||
| } | ||||
| 
 | ||||
| TEST(NumbersTest, Atod) { | ||||
|   double d; | ||||
|   EXPECT_TRUE(absl::SimpleAtod("nan", &d)); | ||||
|   EXPECT_TRUE(std::isnan(d)); | ||||
| } | ||||
| 
 | ||||
| TEST(NumbersTest, Atoenum) { | ||||
|   enum E01 { | ||||
|     E01_zero = 0, | ||||
|  | @ -719,6 +752,51 @@ TEST(stringtest, safe_strtou128_random) { | |||
|     EXPECT_FALSE(parse_func(s, &parsed_value, base)); | ||||
|   } | ||||
| } | ||||
| TEST(stringtest, safe_strto128_random) { | ||||
|   // random number generators don't work for int128, and
 | ||||
|   // int128 can be streamed but not StrCat'd, so this code must be custom
 | ||||
|   // implemented for int128, but is generally the same as what's above.
 | ||||
|   // test_random_integer_parse_base<absl::int128>(
 | ||||
|   //     &absl::numbers_internal::safe_strto128_base);
 | ||||
|   using RandomEngine = std::minstd_rand0; | ||||
|   using IntType = absl::int128; | ||||
|   constexpr auto parse_func = &absl::numbers_internal::safe_strto128_base; | ||||
| 
 | ||||
|   std::random_device rd; | ||||
|   RandomEngine rng(rd()); | ||||
|   std::uniform_int_distribution<int64_t> random_int64( | ||||
|       std::numeric_limits<int64_t>::min()); | ||||
|   std::uniform_int_distribution<uint64_t> random_uint64( | ||||
|       std::numeric_limits<uint64_t>::min()); | ||||
|   std::uniform_int_distribution<int> random_base(2, 35); | ||||
| 
 | ||||
|   for (size_t i = 0; i < kNumRandomTests; ++i) { | ||||
|     int64_t high = random_int64(rng); | ||||
|     uint64_t low = random_uint64(rng); | ||||
|     IntType value = absl::MakeInt128(high, low); | ||||
| 
 | ||||
|     int base = random_base(rng); | ||||
|     std::string str_value; | ||||
|     EXPECT_TRUE(Itoa<IntType>(value, base, &str_value)); | ||||
|     IntType parsed_value; | ||||
| 
 | ||||
|     // Test successful parse
 | ||||
|     EXPECT_TRUE(parse_func(str_value, &parsed_value, base)); | ||||
|     EXPECT_EQ(parsed_value, value); | ||||
| 
 | ||||
|     // Test overflow
 | ||||
|     std::string s; | ||||
|     absl::strings_internal::OStringStream(&s) | ||||
|         << std::numeric_limits<IntType>::max() << value; | ||||
|     EXPECT_FALSE(parse_func(s, &parsed_value, base)); | ||||
| 
 | ||||
|     // Test underflow
 | ||||
|     s.clear(); | ||||
|     absl::strings_internal::OStringStream(&s) | ||||
|         << std::numeric_limits<IntType>::min() << value; | ||||
|     EXPECT_FALSE(parse_func(s, &parsed_value, base)); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| TEST(stringtest, safe_strtou32_base) { | ||||
|   for (int i = 0; strtouint32_test_cases()[i].str != nullptr; ++i) { | ||||
|  |  | |||
|  | @ -141,12 +141,12 @@ namespace strings_internal { | |||
| std::string CatPieces(std::initializer_list<absl::string_view> pieces) { | ||||
|   std::string result; | ||||
|   size_t total_size = 0; | ||||
|   for (const absl::string_view piece : pieces) total_size += piece.size(); | ||||
|   for (const absl::string_view& piece : pieces) total_size += piece.size(); | ||||
|   strings_internal::STLStringResizeUninitialized(&result, total_size); | ||||
| 
 | ||||
|   char* const begin = &result[0]; | ||||
|   char* out = begin; | ||||
|   for (const absl::string_view piece : pieces) { | ||||
|   for (const absl::string_view& piece : pieces) { | ||||
|     const size_t this_size = piece.size(); | ||||
|     if (this_size != 0) { | ||||
|       memcpy(out, piece.data(), this_size); | ||||
|  | @ -170,7 +170,7 @@ void AppendPieces(std::string* dest, | |||
|                   std::initializer_list<absl::string_view> pieces) { | ||||
|   size_t old_size = dest->size(); | ||||
|   size_t total_size = old_size; | ||||
|   for (const absl::string_view piece : pieces) { | ||||
|   for (const absl::string_view& piece : pieces) { | ||||
|     ASSERT_NO_OVERLAP(*dest, piece); | ||||
|     total_size += piece.size(); | ||||
|   } | ||||
|  | @ -178,7 +178,7 @@ void AppendPieces(std::string* dest, | |||
| 
 | ||||
|   char* const begin = &(*dest)[0]; | ||||
|   char* out = begin + old_size; | ||||
|   for (const absl::string_view piece : pieces) { | ||||
|   for (const absl::string_view& piece : pieces) { | ||||
|     const size_t this_size = piece.size(); | ||||
|     if (this_size != 0) { | ||||
|       memcpy(out, piece.data(), this_size); | ||||
|  |  | |||
|  | @ -137,4 +137,51 @@ void BM_DoubleToString_By_SixDigits(benchmark::State& state) { | |||
| } | ||||
| BENCHMARK(BM_DoubleToString_By_SixDigits); | ||||
| 
 | ||||
| template <typename... Chunks> | ||||
| void BM_StrAppendImpl(benchmark::State& state, size_t total_bytes, | ||||
|                       Chunks... chunks) { | ||||
|   for (auto s : state) { | ||||
|     std::string result; | ||||
|     while (result.size() < total_bytes) { | ||||
|       absl::StrAppend(&result, chunks...); | ||||
|       benchmark::DoNotOptimize(result); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void BM_StrAppend(benchmark::State& state) { | ||||
|   const int total_bytes = state.range(0); | ||||
|   const int chunks_at_a_time = state.range(1); | ||||
|   const absl::string_view kChunk = "0123456789"; | ||||
| 
 | ||||
|   switch (chunks_at_a_time) { | ||||
|     case 1: | ||||
|       return BM_StrAppendImpl(state, total_bytes, kChunk); | ||||
|     case 2: | ||||
|       return BM_StrAppendImpl(state, total_bytes, kChunk, kChunk); | ||||
|     case 4: | ||||
|       return BM_StrAppendImpl(state, total_bytes, kChunk, kChunk, kChunk, | ||||
|                               kChunk); | ||||
|     case 8: | ||||
|       return BM_StrAppendImpl(state, total_bytes, kChunk, kChunk, kChunk, | ||||
|                               kChunk, kChunk, kChunk, kChunk, kChunk); | ||||
|     default: | ||||
|       std::abort(); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| template <typename B> | ||||
| void StrAppendConfig(B* benchmark) { | ||||
|   for (int bytes : {10, 100, 1000, 10000}) { | ||||
|     for (int chunks : {1, 2, 4, 8}) { | ||||
|       // Only add the ones that divide properly. Otherwise we are over counting.
 | ||||
|       if (bytes % (10 * chunks) == 0) { | ||||
|         benchmark->Args({bytes, chunks}); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| BENCHMARK(BM_StrAppend)->Apply(StrAppendConfig); | ||||
| 
 | ||||
| }  // namespace
 | ||||
|  |  | |||
							
								
								
									
										270
									
								
								third_party/abseil_cpp/absl/strings/str_format.h
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										270
									
								
								third_party/abseil_cpp/absl/strings/str_format.h
									
										
									
									
										vendored
									
									
								
							|  | @ -63,6 +63,9 @@ | |||
| //     loosely typed. `FormatUntyped()` is not a template and does not perform
 | ||||
| //     any compile-time checking of the format 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.  See "StrFormat Extensions" below.
 | ||||
| 
 | ||||
| #ifndef ABSL_STRINGS_STR_FORMAT_H_ | ||||
| #define ABSL_STRINGS_STR_FORMAT_H_ | ||||
|  | @ -278,9 +281,36 @@ using FormatSpec = str_format_internal::FormatSpecTemplate< | |||
| //   } else {
 | ||||
| //     ... error case ...
 | ||||
| //   }
 | ||||
| 
 | ||||
| #if defined(__cpp_nontype_template_parameter_auto) | ||||
| // If C++17 is available, an 'extended' format is also allowed that can specify
 | ||||
| // multiple conversion characters per format argument, using a combination of
 | ||||
| // `absl::FormatConversionCharSet` enum values (logically a set union)
 | ||||
| //  via the `|` operator. (Single character-based arguments are still accepted,
 | ||||
| // but cannot be combined). Some common conversions also have predefined enum
 | ||||
| // values, such as `absl::FormatConversionCharSet::kIntegral`.
 | ||||
| //
 | ||||
| // Example:
 | ||||
| //   // Extended format supports multiple conversion characters per argument,
 | ||||
| //   // specified via a combination of `FormatConversionCharSet` enums.
 | ||||
| //   using MyFormat = absl::ParsedFormat<absl::FormatConversionCharSet::d |
 | ||||
| //                                       absl::FormatConversionCharSet::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);
 | ||||
| template <auto... Conv> | ||||
| using ParsedFormat = absl::str_format_internal::ExtendedParsedFormat< | ||||
|     absl::str_format_internal::ToFormatConversionCharSet(Conv)...>; | ||||
| #else | ||||
| template <char... Conv> | ||||
| using ParsedFormat = str_format_internal::ExtendedParsedFormat< | ||||
|     absl::str_format_internal::ToFormatConversionCharSet(Conv)...>; | ||||
| #endif  // defined(__cpp_nontype_template_parameter_auto)
 | ||||
| 
 | ||||
| // StrFormat()
 | ||||
| //
 | ||||
|  | @ -537,6 +567,246 @@ ABSL_MUST_USE_RESULT inline bool FormatUntyped( | |||
|       str_format_internal::UntypedFormatSpecImpl::Extract(format), args); | ||||
| } | ||||
| 
 | ||||
| //------------------------------------------------------------------------------
 | ||||
| // StrFormat Extensions
 | ||||
| //------------------------------------------------------------------------------
 | ||||
| //
 | ||||
| // AbslFormatConvert()
 | ||||
| //
 | ||||
| // The StrFormat library provides a customization API for formatting
 | ||||
| // user-defined types using absl::StrFormat(). The API relies on detecting an
 | ||||
| // overload in the user-defined type's namespace of a free (non-member)
 | ||||
| // `AbslFormatConvert()` function, usually as a friend definition with the
 | ||||
| // following signature:
 | ||||
| //
 | ||||
| // absl::FormatConvertResult<...> AbslFormatConvert(
 | ||||
| //     const X& value,
 | ||||
| //     const absl::FormatConversionSpec& spec,
 | ||||
| //     absl::FormatSink *sink);
 | ||||
| //
 | ||||
| // An `AbslFormatConvert()` overload for a type should only be declared in the
 | ||||
| // same file and namespace as said type.
 | ||||
| //
 | ||||
| // The abstractions within this definition include:
 | ||||
| //
 | ||||
| // * An `absl::FormatConversionSpec` to specify the fields to pull from a
 | ||||
| //   user-defined type's format string
 | ||||
| // * An `absl::FormatSink` to hold the converted string data during the
 | ||||
| //   conversion process.
 | ||||
| // * An `absl::FormatConvertResult` to hold the status of the returned
 | ||||
| //   formatting operation
 | ||||
| //
 | ||||
| // The return type encodes all the conversion characters that your
 | ||||
| // AbslFormatConvert() routine accepts.  The return value should be {true}.
 | ||||
| // A return value of {false} will result in `StrFormat()` returning
 | ||||
| // an empty string.  This result will be propagated to the result of
 | ||||
| // `FormatUntyped`.
 | ||||
| //
 | ||||
| // Example:
 | ||||
| //
 | ||||
| // struct Point {
 | ||||
| //   // To add formatting support to `Point`, we simply need to add a free
 | ||||
| //   // (non-member) function `AbslFormatConvert()`.  This method interprets
 | ||||
| //   // `spec` to print in the request format. The allowed conversion characters
 | ||||
| //   // can be restricted via the type of the result, in this example
 | ||||
| //   // string and integral formatting are allowed (but not, for instance
 | ||||
| //   // floating point characters like "%f").  You can add such a free function
 | ||||
| //   // using a friend declaration within the body of the class:
 | ||||
| //   friend absl::FormatConvertResult<absl::FormatConversionCharSet::kString |
 | ||||
| //                                    absl::FormatConversionCharSet::kIntegral>
 | ||||
| //   AbslFormatConvert(const Point& p, const absl::FormatConversionSpec& spec,
 | ||||
| //                     absl::FormatSink* s) {
 | ||||
| //     if (spec.conversion_char() == absl::FormatConversionChar::s) {
 | ||||
| //       s->Append(absl::StrCat("x=", p.x, " y=", p.y));
 | ||||
| //     } else {
 | ||||
| //       s->Append(absl::StrCat(p.x, ",", p.y));
 | ||||
| //     }
 | ||||
| //     return {true};
 | ||||
| //   }
 | ||||
| //
 | ||||
| //   int x;
 | ||||
| //   int y;
 | ||||
| // };
 | ||||
| 
 | ||||
| // clang-format off
 | ||||
| 
 | ||||
| // FormatConversionChar
 | ||||
| //
 | ||||
| // Specifies the formatting character provided in the format string
 | ||||
| // passed to `StrFormat()`.
 | ||||
| enum class FormatConversionChar : uint8_t { | ||||
|   c, s,                    // text
 | ||||
|   d, i, o, u, x, X,        // int
 | ||||
|   f, F, e, E, g, G, a, A,  // float
 | ||||
|   n, p                     // misc
 | ||||
| }; | ||||
| // clang-format on
 | ||||
| 
 | ||||
| // FormatConversionSpec
 | ||||
| //
 | ||||
| // Specifies modifications to the conversion of the format string, through use
 | ||||
| // of one or more format flags in the source format string.
 | ||||
| class FormatConversionSpec { | ||||
|  public: | ||||
|   // FormatConversionSpec::is_basic()
 | ||||
|   //
 | ||||
|   // Indicates that width and precision are not specified, and no additional
 | ||||
|   // flags are set for this conversion character in the format string.
 | ||||
|   bool is_basic() const { return impl_.is_basic(); } | ||||
| 
 | ||||
|   // FormatConversionSpec::has_left_flag()
 | ||||
|   //
 | ||||
|   // Indicates whether the result should be left justified for this conversion
 | ||||
|   // character in the format string. This flag is set through use of a '-'
 | ||||
|   // character in the format string. E.g. "%-s"
 | ||||
|   bool has_left_flag() const { return impl_.has_left_flag(); } | ||||
| 
 | ||||
|   // FormatConversionSpec::has_show_pos_flag()
 | ||||
|   //
 | ||||
|   // Indicates whether a sign column is prepended to the result for this
 | ||||
|   // conversion character in the format string, even if the result is positive.
 | ||||
|   // This flag is set through use of a '+' character in the format string.
 | ||||
|   // E.g. "%+d"
 | ||||
|   bool has_show_pos_flag() const { return impl_.has_show_pos_flag(); } | ||||
| 
 | ||||
|   // FormatConversionSpec::has_sign_col_flag()
 | ||||
|   //
 | ||||
|   // Indicates whether a mandatory sign column is added to the result for this
 | ||||
|   // conversion character. This flag is set through use of a space character
 | ||||
|   // (' ') in the format string. E.g. "% i"
 | ||||
|   bool has_sign_col_flag() const { return impl_.has_sign_col_flag(); } | ||||
| 
 | ||||
|   // FormatConversionSpec::has_alt_flag()
 | ||||
|   //
 | ||||
|   // Indicates whether an "alternate" format is applied to the result for this
 | ||||
|   // conversion character. Alternative forms depend on the type of conversion
 | ||||
|   // character, and unallowed alternatives are undefined. This flag is set
 | ||||
|   // through use of a '#' character in the format string. E.g. "%#h"
 | ||||
|   bool has_alt_flag() const { return impl_.has_alt_flag(); } | ||||
| 
 | ||||
|   // FormatConversionSpec::has_zero_flag()
 | ||||
|   //
 | ||||
|   // Indicates whether zeroes should be prepended to the result for this
 | ||||
|   // conversion character instead of spaces. This flag is set through use of the
 | ||||
|   // '0' character in the format string. E.g. "%0f"
 | ||||
|   bool has_zero_flag() const { return impl_.has_zero_flag(); } | ||||
| 
 | ||||
|   // FormatConversionSpec::conversion_char()
 | ||||
|   //
 | ||||
|   // Returns the underlying conversion character.
 | ||||
|   FormatConversionChar conversion_char() const { | ||||
|     return impl_.conversion_char(); | ||||
|   } | ||||
| 
 | ||||
|   // FormatConversionSpec::width()
 | ||||
|   //
 | ||||
|   // Returns the specified width (indicated through use of a non-zero integer
 | ||||
|   // value or '*' character) of the conversion character. If width is
 | ||||
|   // unspecified, it returns a negative value.
 | ||||
|   int width() const { return impl_.width(); } | ||||
| 
 | ||||
|   // FormatConversionSpec::precision()
 | ||||
|   //
 | ||||
|   // Returns the specified precision (through use of the '.' character followed
 | ||||
|   // by a non-zero integer value or '*' character) of the conversion character.
 | ||||
|   // If precision is unspecified, it returns a negative value.
 | ||||
|   int precision() const { return impl_.precision(); } | ||||
| 
 | ||||
|  private: | ||||
|   explicit FormatConversionSpec( | ||||
|       str_format_internal::FormatConversionSpecImpl impl) | ||||
|       : impl_(impl) {} | ||||
| 
 | ||||
|   friend str_format_internal::FormatConversionSpecImpl; | ||||
| 
 | ||||
|   absl::str_format_internal::FormatConversionSpecImpl impl_; | ||||
| }; | ||||
| 
 | ||||
| // Type safe OR operator for FormatConversionCharSet to allow accepting multiple
 | ||||
| // conversion chars in custom format converters.
 | ||||
| constexpr FormatConversionCharSet operator|(FormatConversionCharSet a, | ||||
|                                             FormatConversionCharSet b) { | ||||
|   return static_cast<FormatConversionCharSet>(static_cast<uint64_t>(a) | | ||||
|                                               static_cast<uint64_t>(b)); | ||||
| } | ||||
| 
 | ||||
| // FormatConversionCharSet
 | ||||
| //
 | ||||
| // Specifies the _accepted_ conversion types as a template parameter to
 | ||||
| // FormatConvertResult for custom implementations of `AbslFormatConvert`.
 | ||||
| // Note the helper predefined alias definitions (kIntegral, etc.) below.
 | ||||
| enum class FormatConversionCharSet : uint64_t { | ||||
|   // text
 | ||||
|   c = str_format_internal::FormatConversionCharToConvInt('c'), | ||||
|   s = str_format_internal::FormatConversionCharToConvInt('s'), | ||||
|   // integer
 | ||||
|   d = str_format_internal::FormatConversionCharToConvInt('d'), | ||||
|   i = str_format_internal::FormatConversionCharToConvInt('i'), | ||||
|   o = str_format_internal::FormatConversionCharToConvInt('o'), | ||||
|   u = str_format_internal::FormatConversionCharToConvInt('u'), | ||||
|   x = str_format_internal::FormatConversionCharToConvInt('x'), | ||||
|   X = str_format_internal::FormatConversionCharToConvInt('X'), | ||||
|   // Float
 | ||||
|   f = str_format_internal::FormatConversionCharToConvInt('f'), | ||||
|   F = str_format_internal::FormatConversionCharToConvInt('F'), | ||||
|   e = str_format_internal::FormatConversionCharToConvInt('e'), | ||||
|   E = str_format_internal::FormatConversionCharToConvInt('E'), | ||||
|   g = str_format_internal::FormatConversionCharToConvInt('g'), | ||||
|   G = str_format_internal::FormatConversionCharToConvInt('G'), | ||||
|   a = str_format_internal::FormatConversionCharToConvInt('a'), | ||||
|   A = str_format_internal::FormatConversionCharToConvInt('A'), | ||||
|   // misc
 | ||||
|   n = str_format_internal::FormatConversionCharToConvInt('n'), | ||||
|   p = str_format_internal::FormatConversionCharToConvInt('p'), | ||||
| 
 | ||||
|   // Used for width/precision '*' specification.
 | ||||
|   kStar = static_cast<uint64_t>( | ||||
|       absl::str_format_internal::FormatConversionCharSetInternal::kStar), | ||||
|   // Some predefined values:
 | ||||
|   kIntegral = d | i | u | o | x | X, | ||||
|   kFloating = a | e | f | g | A | E | F | G, | ||||
|   kNumeric = kIntegral | kFloating, | ||||
|   kString = s, | ||||
|   kPointer = p, | ||||
| }; | ||||
| 
 | ||||
| // FormatSink
 | ||||
| //
 | ||||
| // An abstraction to which conversions write their string data.
 | ||||
| //
 | ||||
| class FormatSink { | ||||
|  public: | ||||
|   // Appends `count` copies of `ch`.
 | ||||
|   void Append(size_t count, char ch) { sink_->Append(count, ch); } | ||||
| 
 | ||||
|   void Append(string_view v) { sink_->Append(v); } | ||||
| 
 | ||||
|   // Appends the first `precision` bytes of `v`. If this is less than
 | ||||
|   // `width`, spaces will be appended first (if `left` is false), or
 | ||||
|   // after (if `left` is true) to ensure the total amount appended is
 | ||||
|   // at least `width`.
 | ||||
|   bool PutPaddedString(string_view v, int width, int precision, bool left) { | ||||
|     return sink_->PutPaddedString(v, width, precision, left); | ||||
|   } | ||||
| 
 | ||||
|  private: | ||||
|   friend str_format_internal::FormatSinkImpl; | ||||
|   explicit FormatSink(str_format_internal::FormatSinkImpl* s) : sink_(s) {} | ||||
|   str_format_internal::FormatSinkImpl* sink_; | ||||
| }; | ||||
| 
 | ||||
| // FormatConvertResult
 | ||||
| //
 | ||||
| // Indicates whether a call to AbslFormatConvert() was successful.
 | ||||
| // This return type informs the StrFormat extension framework (through
 | ||||
| // ADL but using the return type) of what conversion characters are supported.
 | ||||
| // It is strongly discouraged to return {false}, as this will result in an
 | ||||
| // empty string in StrFormat.
 | ||||
| template <FormatConversionCharSet C> | ||||
| struct FormatConvertResult { | ||||
|   bool value; | ||||
| }; | ||||
| 
 | ||||
| ABSL_NAMESPACE_END | ||||
| }  // namespace absl
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,3 +1,16 @@ | |||
| // Copyright 2020 The Abseil Authors.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //      https://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| #include "absl/strings/str_format.h" | ||||
| 
 | ||||
|  | @ -16,7 +29,6 @@ namespace absl { | |||
| ABSL_NAMESPACE_BEGIN | ||||
| namespace { | ||||
| using str_format_internal::FormatArgImpl; | ||||
| using str_format_internal::FormatConversionCharSetInternal; | ||||
| 
 | ||||
| using FormatEntryPointTest = ::testing::Test; | ||||
| 
 | ||||
|  | @ -537,46 +549,90 @@ TEST_F(ParsedFormatTest, SimpleUncheckedIncorrect) { | |||
|   EXPECT_FALSE((ParsedFormat<'s', 'd', 'g'>::New(format))); | ||||
| } | ||||
| 
 | ||||
| using absl::str_format_internal::FormatConversionCharSet; | ||||
| #if defined(__cpp_nontype_template_parameter_auto) | ||||
| 
 | ||||
| template <auto T> | ||||
| std::true_type IsValidParsedFormatArgTest(ParsedFormat<T>*); | ||||
| 
 | ||||
| template <auto T> | ||||
| std::false_type IsValidParsedFormatArgTest(...); | ||||
| 
 | ||||
| template <auto T> | ||||
| using IsValidParsedFormatArg = decltype(IsValidParsedFormatArgTest<T>(nullptr)); | ||||
| 
 | ||||
| TEST_F(ParsedFormatTest, OnlyValidTypesAllowed) { | ||||
|   ASSERT_TRUE(IsValidParsedFormatArg<'c'>::value); | ||||
| 
 | ||||
|   ASSERT_TRUE(IsValidParsedFormatArg<FormatConversionCharSet::d>::value); | ||||
| 
 | ||||
|   ASSERT_TRUE(IsValidParsedFormatArg<absl::FormatConversionCharSet::d | | ||||
|                                      absl::FormatConversionCharSet::x>::value); | ||||
|   ASSERT_TRUE( | ||||
|       IsValidParsedFormatArg<absl::FormatConversionCharSet::kIntegral>::value); | ||||
| 
 | ||||
|   // This is an easy mistake to make, however, this will reduce to an integer
 | ||||
|   // which has no meaning, so we need to ensure it doesn't compile.
 | ||||
|   ASSERT_FALSE(IsValidParsedFormatArg<'x' | 'd'>::value); | ||||
| 
 | ||||
|   // For now, we disallow construction based on ConversionChar (rather than
 | ||||
|   // CharSet)
 | ||||
|   ASSERT_FALSE(IsValidParsedFormatArg<absl::FormatConversionChar::d>::value); | ||||
| } | ||||
| 
 | ||||
| TEST_F(ParsedFormatTest, ExtendedTyping) { | ||||
|   EXPECT_FALSE(ParsedFormat<FormatConversionCharSet::d>::New("")); | ||||
|   ASSERT_TRUE(ParsedFormat<absl::FormatConversionCharSet::d>::New("%d")); | ||||
|   auto v1 = ParsedFormat<'d', absl::FormatConversionCharSet::s>::New("%d%s"); | ||||
|   ASSERT_TRUE(v1); | ||||
|   auto v2 = ParsedFormat<absl::FormatConversionCharSet::d, 's'>::New("%d%s"); | ||||
|   ASSERT_TRUE(v2); | ||||
|   auto v3 = ParsedFormat<absl::FormatConversionCharSet::d | | ||||
|                              absl::FormatConversionCharSet::s, | ||||
|                          's'>::New("%d%s"); | ||||
|   ASSERT_TRUE(v3); | ||||
|   auto v4 = ParsedFormat<absl::FormatConversionCharSet::d | | ||||
|                              absl::FormatConversionCharSet::s, | ||||
|                          's'>::New("%s%s"); | ||||
|   ASSERT_TRUE(v4); | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
| TEST_F(ParsedFormatTest, UncheckedCorrect) { | ||||
|   auto f = | ||||
|       ExtendedParsedFormat<FormatConversionCharSetInternal::d>::New("ABC%dDEF"); | ||||
|       ExtendedParsedFormat<absl::FormatConversionCharSet::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< | ||||
|       FormatConversionCharSetInternal::kString, | ||||
|       FormatConversionCharSetInternal::d, | ||||
|       FormatConversionCharSetInternal::kFloating>::New(format); | ||||
|       absl::FormatConversionCharSet::kString, absl::FormatConversionCharSet::d, | ||||
|       absl::FormatConversionCharSet::kFloating>::New(format); | ||||
| 
 | ||||
|   ASSERT_TRUE(f2); | ||||
|   EXPECT_EQ("{s:1$s}[FFF]{d:2$d}[ZZZ]{f:3$f}", SummarizeParsedFormat(*f2)); | ||||
| 
 | ||||
|   f2 = ExtendedParsedFormat< | ||||
|       FormatConversionCharSetInternal::kString, | ||||
|       FormatConversionCharSetInternal::d, | ||||
|       FormatConversionCharSetInternal::kFloating>::New("%s %d %f"); | ||||
|       absl::FormatConversionCharSet::kString, absl::FormatConversionCharSet::d, | ||||
|       absl::FormatConversionCharSet::kFloating>::New("%s %d %f"); | ||||
| 
 | ||||
|   ASSERT_TRUE(f2); | ||||
|   EXPECT_EQ("{s:1$s}[ ]{d:2$d}[ ]{f:3$f}", SummarizeParsedFormat(*f2)); | ||||
| 
 | ||||
|   auto star = | ||||
|       ExtendedParsedFormat<FormatConversionCharSetInternal::kStar, | ||||
|                            FormatConversionCharSetInternal::d>::New("%*d"); | ||||
|       ExtendedParsedFormat<absl::FormatConversionCharSet::kStar, | ||||
|                            absl::FormatConversionCharSet::d>::New("%*d"); | ||||
|   ASSERT_TRUE(star); | ||||
|   EXPECT_EQ("{*d:2$1$*d}", SummarizeParsedFormat(*star)); | ||||
| 
 | ||||
|   auto dollar = ExtendedParsedFormat< | ||||
|       FormatConversionCharSetInternal::d, | ||||
|       FormatConversionCharSetInternal::s>::New("%2$s %1$d"); | ||||
|   auto dollar = | ||||
|       ExtendedParsedFormat<absl::FormatConversionCharSet::d, | ||||
|                            absl::FormatConversionCharSet::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< | ||||
|       FormatConversionCharSetInternal::d, | ||||
|       FormatConversionCharSetInternal::s>::New("%2$s %1$d %1$d"); | ||||
|       absl::FormatConversionCharSet::d, | ||||
|       absl::FormatConversionCharSet::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)); | ||||
|  | @ -584,62 +640,61 @@ TEST_F(ParsedFormatTest, UncheckedCorrect) { | |||
| 
 | ||||
| TEST_F(ParsedFormatTest, UncheckedIgnoredArgs) { | ||||
|   EXPECT_FALSE( | ||||
|       (ExtendedParsedFormat<FormatConversionCharSetInternal::d, | ||||
|                             FormatConversionCharSetInternal::s>::New("ABC"))); | ||||
|       (ExtendedParsedFormat<absl::FormatConversionCharSet::d, | ||||
|                             absl::FormatConversionCharSet::s>::New("ABC"))); | ||||
|   EXPECT_FALSE( | ||||
|       (ExtendedParsedFormat<FormatConversionCharSetInternal::d, | ||||
|                             FormatConversionCharSetInternal::s>::New("%dABC"))); | ||||
|   EXPECT_FALSE((ExtendedParsedFormat< | ||||
|                 FormatConversionCharSetInternal::d, | ||||
|                 FormatConversionCharSetInternal::s>::New("ABC%2$s"))); | ||||
|       (ExtendedParsedFormat<absl::FormatConversionCharSet::d, | ||||
|                             absl::FormatConversionCharSet::s>::New("%dABC"))); | ||||
|   EXPECT_FALSE( | ||||
|       (ExtendedParsedFormat<absl::FormatConversionCharSet::d, | ||||
|                             absl::FormatConversionCharSet::s>::New("ABC%2$s"))); | ||||
|   auto f = ExtendedParsedFormat< | ||||
|       FormatConversionCharSetInternal::d, | ||||
|       FormatConversionCharSetInternal::s>::NewAllowIgnored("ABC"); | ||||
|       absl::FormatConversionCharSet::d, | ||||
|       absl::FormatConversionCharSet::s>::NewAllowIgnored("ABC"); | ||||
|   ASSERT_TRUE(f); | ||||
|   EXPECT_EQ("[ABC]", SummarizeParsedFormat(*f)); | ||||
|   f = ExtendedParsedFormat< | ||||
|       FormatConversionCharSetInternal::d, | ||||
|       FormatConversionCharSetInternal::s>::NewAllowIgnored("%dABC"); | ||||
|       absl::FormatConversionCharSet::d, | ||||
|       absl::FormatConversionCharSet::s>::NewAllowIgnored("%dABC"); | ||||
|   ASSERT_TRUE(f); | ||||
|   EXPECT_EQ("{d:1$d}[ABC]", SummarizeParsedFormat(*f)); | ||||
|   f = ExtendedParsedFormat< | ||||
|       FormatConversionCharSetInternal::d, | ||||
|       FormatConversionCharSetInternal::s>::NewAllowIgnored("ABC%2$s"); | ||||
|       absl::FormatConversionCharSet::d, | ||||
|       absl::FormatConversionCharSet::s>::NewAllowIgnored("ABC%2$s"); | ||||
|   ASSERT_TRUE(f); | ||||
|   EXPECT_EQ("[ABC]{2$s:2$s}", SummarizeParsedFormat(*f)); | ||||
| } | ||||
| 
 | ||||
| TEST_F(ParsedFormatTest, UncheckedMultipleTypes) { | ||||
|   auto dx = ExtendedParsedFormat< | ||||
|       FormatConversionCharSetInternal::d | | ||||
|       FormatConversionCharSetInternal::x>::New("%1$d %1$x"); | ||||
|   auto dx = | ||||
|       ExtendedParsedFormat<absl::FormatConversionCharSet::d | | ||||
|                            absl::FormatConversionCharSet::x>::New("%1$d %1$x"); | ||||
|   EXPECT_TRUE(dx); | ||||
|   EXPECT_EQ("{1$d:1$d}[ ]{1$x:1$x}", SummarizeParsedFormat(*dx)); | ||||
| 
 | ||||
|   dx = ExtendedParsedFormat<FormatConversionCharSetInternal::d | | ||||
|                             FormatConversionCharSetInternal::x>::New("%1$d"); | ||||
|   dx = ExtendedParsedFormat<absl::FormatConversionCharSet::d | | ||||
|                             absl::FormatConversionCharSet::x>::New("%1$d"); | ||||
|   EXPECT_TRUE(dx); | ||||
|   EXPECT_EQ("{1$d:1$d}", SummarizeParsedFormat(*dx)); | ||||
| } | ||||
| 
 | ||||
| TEST_F(ParsedFormatTest, UncheckedIncorrect) { | ||||
|   EXPECT_FALSE( | ||||
|       ExtendedParsedFormat<FormatConversionCharSetInternal::d>::New("")); | ||||
|   EXPECT_FALSE(ExtendedParsedFormat<absl::FormatConversionCharSet::d>::New("")); | ||||
| 
 | ||||
|   EXPECT_FALSE(ExtendedParsedFormat<FormatConversionCharSetInternal::d>::New( | ||||
|   EXPECT_FALSE(ExtendedParsedFormat<absl::FormatConversionCharSet::d>::New( | ||||
|       "ABC%dDEF%d")); | ||||
| 
 | ||||
|   std::string format = "%sFFF%dZZZ%f"; | ||||
|   EXPECT_FALSE( | ||||
|       (ExtendedParsedFormat<FormatConversionCharSetInternal::s, | ||||
|                             FormatConversionCharSetInternal::d, | ||||
|                             FormatConversionCharSetInternal::g>::New(format))); | ||||
|       (ExtendedParsedFormat<absl::FormatConversionCharSet::s, | ||||
|                             absl::FormatConversionCharSet::d, | ||||
|                             absl::FormatConversionCharSet::g>::New(format))); | ||||
| } | ||||
| 
 | ||||
| TEST_F(ParsedFormatTest, RegressionMixPositional) { | ||||
|   EXPECT_FALSE((ExtendedParsedFormat< | ||||
|                 FormatConversionCharSetInternal::d, | ||||
|                 FormatConversionCharSetInternal::o>::New("%1$d %o"))); | ||||
|   EXPECT_FALSE( | ||||
|       (ExtendedParsedFormat<absl::FormatConversionCharSet::d, | ||||
|                             absl::FormatConversionCharSet::o>::New("%1$d %o"))); | ||||
| } | ||||
| 
 | ||||
| using FormatWrapperTest = ::testing::Test; | ||||
|  | @ -664,6 +719,38 @@ TEST_F(FormatWrapperTest, ParsedFormat) { | |||
| ABSL_NAMESPACE_END | ||||
| }  // namespace absl
 | ||||
| 
 | ||||
| using FormatExtensionTest = ::testing::Test; | ||||
| 
 | ||||
| struct Point { | ||||
|   friend absl::FormatConvertResult<absl::FormatConversionCharSet::kString | | ||||
|                                    absl::FormatConversionCharSet::kIntegral> | ||||
|   AbslFormatConvert(const Point& p, const absl::FormatConversionSpec& spec, | ||||
|                     absl::FormatSink* s) { | ||||
|     if (spec.conversion_char() == absl::FormatConversionChar::s) { | ||||
|       s->Append(absl::StrCat("x=", p.x, " y=", p.y)); | ||||
|     } else { | ||||
|       s->Append(absl::StrCat(p.x, ",", p.y)); | ||||
|     } | ||||
|     return {true}; | ||||
|   } | ||||
| 
 | ||||
|   int x = 10; | ||||
|   int y = 20; | ||||
| }; | ||||
| 
 | ||||
| TEST_F(FormatExtensionTest, AbslFormatConvertExample) { | ||||
|   Point p; | ||||
|   EXPECT_EQ(absl::StrFormat("a %s z", p), "a x=10 y=20 z"); | ||||
|   EXPECT_EQ(absl::StrFormat("a %d z", p), "a 10,20 z"); | ||||
| 
 | ||||
|   // Typed formatting will fail to compile an invalid format.
 | ||||
|   // StrFormat("%f", p);  // Does not compile.
 | ||||
|   std::string actual; | ||||
|   absl::UntypedFormatSpec f1("%f"); | ||||
|   // FormatUntyped will return false for bad character.
 | ||||
|   EXPECT_FALSE(absl::FormatUntyped(&actual, f1, {absl::FormatArg(p)})); | ||||
| } | ||||
| 
 | ||||
| // Some codegen thunks that we can use to easily dump the generated assembly for
 | ||||
| // different StrFormat calls.
 | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										42
									
								
								third_party/abseil_cpp/absl/strings/str_split.h
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										42
									
								
								third_party/abseil_cpp/absl/strings/str_split.h
									
										
									
									
										vendored
									
									
								
							|  | @ -369,6 +369,12 @@ struct SkipWhitespace { | |||
|   } | ||||
| }; | ||||
| 
 | ||||
| template <typename T> | ||||
| using EnableSplitIfString = | ||||
|     typename std::enable_if<std::is_same<T, std::string>::value || | ||||
|                             std::is_same<T, const std::string>::value, | ||||
|                             int>::type; | ||||
| 
 | ||||
| //------------------------------------------------------------------------------
 | ||||
| //                                  StrSplit()
 | ||||
| //------------------------------------------------------------------------------
 | ||||
|  | @ -489,22 +495,50 @@ struct SkipWhitespace { | |||
| // Try not to depend on this distinction because the bug may one day be fixed.
 | ||||
| template <typename Delimiter> | ||||
| strings_internal::Splitter< | ||||
|     typename strings_internal::SelectDelimiter<Delimiter>::type, AllowEmpty> | ||||
|     typename strings_internal::SelectDelimiter<Delimiter>::type, AllowEmpty, | ||||
|     absl::string_view> | ||||
| StrSplit(strings_internal::ConvertibleToStringView text, Delimiter d) { | ||||
|   using DelimiterType = | ||||
|       typename strings_internal::SelectDelimiter<Delimiter>::type; | ||||
|   return strings_internal::Splitter<DelimiterType, AllowEmpty>( | ||||
|   return strings_internal::Splitter<DelimiterType, AllowEmpty, | ||||
|                                     absl::string_view>( | ||||
|       text.value(), DelimiterType(d), AllowEmpty()); | ||||
| } | ||||
| 
 | ||||
| template <typename Delimiter, typename StringType, | ||||
|           EnableSplitIfString<StringType> = 0> | ||||
| strings_internal::Splitter< | ||||
|     typename strings_internal::SelectDelimiter<Delimiter>::type, AllowEmpty, | ||||
|     std::string> | ||||
| StrSplit(StringType&& text, Delimiter d) { | ||||
|   using DelimiterType = | ||||
|       typename strings_internal::SelectDelimiter<Delimiter>::type; | ||||
|   return strings_internal::Splitter<DelimiterType, AllowEmpty, std::string>( | ||||
|       std::move(text), DelimiterType(d), AllowEmpty()); | ||||
| } | ||||
| 
 | ||||
| template <typename Delimiter, typename Predicate> | ||||
| strings_internal::Splitter< | ||||
|     typename strings_internal::SelectDelimiter<Delimiter>::type, Predicate> | ||||
|     typename strings_internal::SelectDelimiter<Delimiter>::type, Predicate, | ||||
|     absl::string_view> | ||||
| StrSplit(strings_internal::ConvertibleToStringView text, Delimiter d, | ||||
|          Predicate p) { | ||||
|   using DelimiterType = | ||||
|       typename strings_internal::SelectDelimiter<Delimiter>::type; | ||||
|   return strings_internal::Splitter<DelimiterType, Predicate>( | ||||
|   return strings_internal::Splitter<DelimiterType, Predicate, | ||||
|                                     absl::string_view>( | ||||
|       text.value(), DelimiterType(d), std::move(p)); | ||||
| } | ||||
| 
 | ||||
| template <typename Delimiter, typename Predicate, typename StringType, | ||||
|           EnableSplitIfString<StringType> = 0> | ||||
| strings_internal::Splitter< | ||||
|     typename strings_internal::SelectDelimiter<Delimiter>::type, Predicate, | ||||
|     std::string> | ||||
| StrSplit(StringType&& text, Delimiter d, Predicate p) { | ||||
|   using DelimiterType = | ||||
|       typename strings_internal::SelectDelimiter<Delimiter>::type; | ||||
|   return strings_internal::Splitter<DelimiterType, Predicate, std::string>( | ||||
|       std::move(text), DelimiterType(d), std::move(p)); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -27,7 +27,7 @@ | |||
| 
 | ||||
| #include "gmock/gmock.h" | ||||
| #include "gtest/gtest.h" | ||||
| #include "absl/base/dynamic_annotations.h"  // for RunningOnValgrind
 | ||||
| #include "absl/base/dynamic_annotations.h" | ||||
| #include "absl/base/macros.h" | ||||
| #include "absl/container/flat_hash_map.h" | ||||
| #include "absl/container/node_hash_map.h" | ||||
|  | @ -367,7 +367,7 @@ TEST(SplitIterator, EqualityAsEndCondition) { | |||
| TEST(Splitter, RangeIterators) { | ||||
|   auto splitter = absl::StrSplit("a,b,c", ','); | ||||
|   std::vector<absl::string_view> output; | ||||
|   for (const absl::string_view p : splitter) { | ||||
|   for (const absl::string_view& p : splitter) { | ||||
|     output.push_back(p); | ||||
|   } | ||||
|   EXPECT_THAT(output, ElementsAre("a", "b", "c")); | ||||
|  |  | |||
|  | @ -111,6 +111,11 @@ ABSL_NAMESPACE_BEGIN | |||
| // example, when splitting a string, `std::vector<absl::string_view>` is a
 | ||||
| // natural data type for the output.
 | ||||
| //
 | ||||
| // For another example, a Cord is a non-contiguous, potentially very
 | ||||
| // long string-like object.  The Cord class has an interface that iteratively
 | ||||
| // provides string_view objects that point to the successive pieces of a Cord
 | ||||
| // object.
 | ||||
| //
 | ||||
| // When constructed from a source which is NUL-terminated, the `string_view`
 | ||||
| // itself will not include the NUL-terminator unless a specific size (including
 | ||||
| // the NUL) is passed to the constructor. As a result, common idioms that work
 | ||||
|  | @ -382,6 +387,7 @@ class string_view { | |||
|   // Returns a "substring" of the `string_view` (at offset `pos` and length
 | ||||
|   // `n`) as another string_view. This function throws `std::out_of_bounds` if
 | ||||
|   // `pos > size`.
 | ||||
|   // Use absl::ClippedSubstr if you need a truncating substr operation.
 | ||||
|   constexpr string_view substr(size_type pos, size_type n = npos) const { | ||||
|     return ABSL_PREDICT_FALSE(pos > length_) | ||||
|                ? (base_internal::ThrowStdOutOfRange( | ||||
|  |  | |||
|  | @ -1177,9 +1177,9 @@ TEST(FindOneCharTest, EdgeCases) { | |||
|   EXPECT_EQ(absl::string_view::npos, a.rfind('x')); | ||||
| } | ||||
| 
 | ||||
| #ifndef THREAD_SANITIZER  // Allocates too much memory for tsan.
 | ||||
| #ifndef ABSL_HAVE_THREAD_SANITIZER  // Allocates too much memory for tsan.
 | ||||
| TEST(HugeStringView, TwoPointTwoGB) { | ||||
|   if (sizeof(size_t) <= 4 || RunningOnValgrind()) | ||||
|   if (sizeof(size_t) <= 4) | ||||
|     return; | ||||
|   // Try a huge string piece.
 | ||||
|   const size_t size = size_t{2200} * 1000 * 1000; | ||||
|  | @ -1191,7 +1191,7 @@ TEST(HugeStringView, TwoPointTwoGB) { | |||
|   sp.remove_suffix(2); | ||||
|   EXPECT_EQ(size - 1 - 2, sp.length()); | ||||
| } | ||||
| #endif  // THREAD_SANITIZER
 | ||||
| #endif  // ABSL_HAVE_THREAD_SANITIZER
 | ||||
| 
 | ||||
| #if !defined(NDEBUG) && !defined(ABSL_USES_STD_STRING_VIEW) | ||||
| TEST(NonNegativeLenTest, NonNegativeLen) { | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue