create
This commit is contained in:
357
Pods/abseil/absl/strings/internal/charconv_bigint.cc
generated
Normal file
357
Pods/abseil/absl/strings/internal/charconv_bigint.cc
generated
Normal file
@@ -0,0 +1,357 @@
|
||||
// Copyright 2018 The Abseil Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// 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/charconv_bigint.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace strings_internal {
|
||||
|
||||
namespace {
|
||||
|
||||
// Table containing some large powers of 5, for fast computation.
|
||||
|
||||
// Constant step size for entries in the kLargePowersOfFive table. Each entry
|
||||
// is larger than the previous entry by a factor of 5**kLargePowerOfFiveStep
|
||||
// (or 5**27).
|
||||
//
|
||||
// In other words, the Nth entry in the table is 5**(27*N).
|
||||
//
|
||||
// 5**27 is the largest power of 5 that fits in 64 bits.
|
||||
constexpr int kLargePowerOfFiveStep = 27;
|
||||
|
||||
// The largest legal index into the kLargePowersOfFive table.
|
||||
//
|
||||
// In other words, the largest precomputed power of 5 is 5**(27*20).
|
||||
constexpr int kLargestPowerOfFiveIndex = 20;
|
||||
|
||||
// Table of powers of (5**27), up to (5**27)**20 == 5**540.
|
||||
//
|
||||
// Used to generate large powers of 5 while limiting the number of repeated
|
||||
// multiplications required.
|
||||
//
|
||||
// clang-format off
|
||||
const uint32_t kLargePowersOfFive[] = {
|
||||
// 5**27 (i=1), start=0, end=2
|
||||
0xfa10079dU, 0x6765c793U,
|
||||
// 5**54 (i=2), start=2, end=6
|
||||
0x97d9f649U, 0x6664242dU, 0x29939b14U, 0x29c30f10U,
|
||||
// 5**81 (i=3), start=6, end=12
|
||||
0xc4f809c5U, 0x7bf3f22aU, 0x67bdae34U, 0xad340517U, 0x369d1b5fU, 0x10de1593U,
|
||||
// 5**108 (i=4), start=12, end=20
|
||||
0x92b260d1U, 0x9efff7c7U, 0x81de0ec6U, 0xaeba5d56U, 0x410664a4U, 0x4f40737aU,
|
||||
0x20d3846fU, 0x06d00f73U,
|
||||
// 5**135 (i=5), start=20, end=30
|
||||
0xff1b172dU, 0x13a1d71cU, 0xefa07617U, 0x7f682d3dU, 0xff8c90c0U, 0x3f0131e7U,
|
||||
0x3fdcb9feU, 0x917b0177U, 0x16c407a7U, 0x02c06b9dU,
|
||||
// 5**162 (i=6), start=30, end=42
|
||||
0x960f7199U, 0x056667ecU, 0xe07aefd8U, 0x80f2b9ccU, 0x8273f5e3U, 0xeb9a214aU,
|
||||
0x40b38005U, 0x0e477ad4U, 0x277d08e6U, 0xfa28b11eU, 0xd3f7d784U, 0x011c835bU,
|
||||
// 5**189 (i=7), start=42, end=56
|
||||
0xf723d9d5U, 0x3282d3f3U, 0xe00857d1U, 0x69659d25U, 0x2cf117cfU, 0x24da6d07U,
|
||||
0x954d1417U, 0x3e5d8cedU, 0x7a8bb766U, 0xfd785ae6U, 0x645436d2U, 0x40c78b34U,
|
||||
0x94151217U, 0x0072e9f7U,
|
||||
// 5**216 (i=8), start=56, end=72
|
||||
0x2b416aa1U, 0x7893c5a7U, 0xe37dc6d4U, 0x2bad2beaU, 0xf0fc846cU, 0x7575ae4bU,
|
||||
0x62587b14U, 0x83b67a34U, 0x02110cdbU, 0xf7992f55U, 0x00deb022U, 0xa4a23becU,
|
||||
0x8af5c5cdU, 0xb85b654fU, 0x818df38bU, 0x002e69d2U,
|
||||
// 5**243 (i=9), start=72, end=90
|
||||
0x3518cbbdU, 0x20b0c15fU, 0x38756c2fU, 0xfb5dc3ddU, 0x22ad2d94U, 0xbf35a952U,
|
||||
0xa699192aU, 0x9a613326U, 0xad2a9cedU, 0xd7f48968U, 0xe87dfb54U, 0xc8f05db6U,
|
||||
0x5ef67531U, 0x31c1ab49U, 0xe202ac9fU, 0x9b2957b5U, 0xa143f6d3U, 0x0012bf07U,
|
||||
// 5**270 (i=10), start=90, end=110
|
||||
0x8b971de9U, 0x21aba2e1U, 0x63944362U, 0x57172336U, 0xd9544225U, 0xfb534166U,
|
||||
0x08c563eeU, 0x14640ee2U, 0x24e40d31U, 0x02b06537U, 0x03887f14U, 0x0285e533U,
|
||||
0xb744ef26U, 0x8be3a6c4U, 0x266979b4U, 0x6761ece2U, 0xd9cb39e4U, 0xe67de319U,
|
||||
0x0d39e796U, 0x00079250U,
|
||||
// 5**297 (i=11), start=110, end=132
|
||||
0x260eb6e5U, 0xf414a796U, 0xee1a7491U, 0xdb9368ebU, 0xf50c105bU, 0x59157750U,
|
||||
0x9ed2fb5cU, 0xf6e56d8bU, 0xeaee8d23U, 0x0f319f75U, 0x2aa134d6U, 0xac2908e9U,
|
||||
0xd4413298U, 0x02f02a55U, 0x989d5a7aU, 0x70dde184U, 0xba8040a7U, 0x03200981U,
|
||||
0xbe03b11cU, 0x3c1c2a18U, 0xd60427a1U, 0x00030ee0U,
|
||||
// 5**324 (i=12), start=132, end=156
|
||||
0xce566d71U, 0xf1c4aa25U, 0x4e93ca53U, 0xa72283d0U, 0x551a73eaU, 0x3d0538e2U,
|
||||
0x8da4303fU, 0x6a58de60U, 0x0e660221U, 0x49cf61a6U, 0x8d058fc1U, 0xb9d1a14cU,
|
||||
0x4bab157dU, 0xc85c6932U, 0x518c8b9eU, 0x9b92b8d0U, 0x0d8a0e21U, 0xbd855df9U,
|
||||
0xb3ea59a1U, 0x8da29289U, 0x4584d506U, 0x3752d80fU, 0xb72569c6U, 0x00013c33U,
|
||||
// 5**351 (i=13), start=156, end=182
|
||||
0x190f354dU, 0x83695cfeU, 0xe5a4d0c7U, 0xb60fb7e8U, 0xee5bbcc4U, 0xb922054cU,
|
||||
0xbb4f0d85U, 0x48394028U, 0x1d8957dbU, 0x0d7edb14U, 0x4ecc7587U, 0x505e9e02U,
|
||||
0x4c87f36bU, 0x99e66bd6U, 0x44b9ed35U, 0x753037d4U, 0xe5fe5f27U, 0x2742c203U,
|
||||
0x13b2ed2bU, 0xdc525d2cU, 0xe6fde59aU, 0x77ffb18fU, 0x13c5752cU, 0x08a84bccU,
|
||||
0x859a4940U, 0x00007fb6U,
|
||||
// 5**378 (i=14), start=182, end=210
|
||||
0x4f98cb39U, 0xa60edbbcU, 0x83b5872eU, 0xa501acffU, 0x9cc76f78U, 0xbadd4c73U,
|
||||
0x43e989faU, 0xca7acf80U, 0x2e0c824fU, 0xb19f4ffcU, 0x092fd81cU, 0xe4eb645bU,
|
||||
0xa1ff84c2U, 0x8a5a83baU, 0xa8a1fae9U, 0x1db43609U, 0xb0fed50bU, 0x0dd7d2bdU,
|
||||
0x7d7accd8U, 0x91fa640fU, 0x37dcc6c5U, 0x1c417fd5U, 0xe4d462adU, 0xe8a43399U,
|
||||
0x131bf9a5U, 0x8df54d29U, 0x36547dc1U, 0x00003395U,
|
||||
// 5**405 (i=15), start=210, end=240
|
||||
0x5bd330f5U, 0x77d21967U, 0x1ac481b7U, 0x6be2f7ceU, 0x7f4792a9U, 0xe84c2c52U,
|
||||
0x84592228U, 0x9dcaf829U, 0xdab44ce1U, 0x3d0c311bU, 0x532e297dU, 0x4704e8b4U,
|
||||
0x9cdc32beU, 0x41e64d9dU, 0x7717bea1U, 0xa824c00dU, 0x08f50b27U, 0x0f198d77U,
|
||||
0x49bbfdf0U, 0x025c6c69U, 0xd4e55cd3U, 0xf083602bU, 0xb9f0fecdU, 0xc0864aeaU,
|
||||
0x9cb98681U, 0xaaf620e9U, 0xacb6df30U, 0x4faafe66U, 0x8af13c3bU, 0x000014d5U,
|
||||
// 5**432 (i=16), start=240, end=272
|
||||
0x682bb941U, 0x89a9f297U, 0xcba75d7bU, 0x404217b1U, 0xb4e519e9U, 0xa1bc162bU,
|
||||
0xf7f5910aU, 0x98715af5U, 0x2ff53e57U, 0xe3ef118cU, 0x490c4543U, 0xbc9b1734U,
|
||||
0x2affbe4dU, 0x4cedcb4cU, 0xfb14e99eU, 0x35e34212U, 0xece39c24U, 0x07673ab3U,
|
||||
0xe73115ddU, 0xd15d38e7U, 0x093eed3bU, 0xf8e7eac5U, 0x78a8cc80U, 0x25227aacU,
|
||||
0x3f590551U, 0x413da1cbU, 0xdf643a55U, 0xab65ad44U, 0xd70b23d7U, 0xc672cd76U,
|
||||
0x3364ea62U, 0x0000086aU,
|
||||
// 5**459 (i=17), start=272, end=306
|
||||
0x22f163ddU, 0x23cf07acU, 0xbe2af6c2U, 0xf412f6f6U, 0xc3ff541eU, 0x6eeaf7deU,
|
||||
0xa47047e0U, 0x408cda92U, 0x0f0eeb08U, 0x56deba9dU, 0xcfc6b090U, 0x8bbbdf04U,
|
||||
0x3933cdb3U, 0x9e7bb67dU, 0x9f297035U, 0x38946244U, 0xee1d37bbU, 0xde898174U,
|
||||
0x63f3559dU, 0x705b72fbU, 0x138d27d9U, 0xf8603a78U, 0x735eec44U, 0xe30987d5U,
|
||||
0xc6d38070U, 0x9cfe548eU, 0x9ff01422U, 0x7c564aa8U, 0x91cc60baU, 0xcbc3565dU,
|
||||
0x7550a50bU, 0x6909aeadU, 0x13234c45U, 0x00000366U,
|
||||
// 5**486 (i=18), start=306, end=342
|
||||
0x17954989U, 0x3a7d7709U, 0x98042de5U, 0xa9011443U, 0x45e723c2U, 0x269ffd6fU,
|
||||
0x58852a46U, 0xaaa1042aU, 0x2eee8153U, 0xb2b6c39eU, 0xaf845b65U, 0xf6c365d7U,
|
||||
0xe4cffb2bU, 0xc840e90cU, 0xabea8abbU, 0x5c58f8d2U, 0x5c19fa3aU, 0x4670910aU,
|
||||
0x4449f21cU, 0xefa645b3U, 0xcc427decU, 0x083c3d73U, 0x467cb413U, 0x6fe10ae4U,
|
||||
0x3caffc72U, 0x9f8da55eU, 0x5e5c8ea7U, 0x490594bbU, 0xf0871b0bU, 0xdd89816cU,
|
||||
0x8e931df8U, 0xe85ce1c9U, 0xcca090a5U, 0x575fa16bU, 0x6b9f106cU, 0x0000015fU,
|
||||
// 5**513 (i=19), start=342, end=380
|
||||
0xee20d805U, 0x57bc3c07U, 0xcdea624eU, 0xd3f0f52dU, 0x9924b4f4U, 0xcf968640U,
|
||||
0x61d41962U, 0xe87fb464U, 0xeaaf51c7U, 0x564c8b60U, 0xccda4028U, 0x529428bbU,
|
||||
0x313a1fa8U, 0x96bd0f94U, 0x7a82ebaaU, 0xad99e7e9U, 0xf2668cd4U, 0xbe33a45eU,
|
||||
0xfd0db669U, 0x87ee369fU, 0xd3ec20edU, 0x9c4d7db7U, 0xdedcf0d8U, 0x7cd2ca64U,
|
||||
0xe25a6577U, 0x61003fd4U, 0xe56f54ccU, 0x10b7c748U, 0x40526e5eU, 0x7300ae87U,
|
||||
0x5c439261U, 0x2c0ff469U, 0xbf723f12U, 0xb2379b61U, 0xbf59b4f5U, 0xc91b1c3fU,
|
||||
0xf0046d27U, 0x0000008dU,
|
||||
// 5**540 (i=20), start=380, end=420
|
||||
0x525c9e11U, 0xf4e0eb41U, 0xebb2895dU, 0x5da512f9U, 0x7d9b29d4U, 0x452f4edcU,
|
||||
0x0b90bc37U, 0x341777cbU, 0x63d269afU, 0x1da77929U, 0x0a5c1826U, 0x77991898U,
|
||||
0x5aeddf86U, 0xf853a877U, 0x538c31ccU, 0xe84896daU, 0xb7a0010bU, 0x17ef4de5U,
|
||||
0xa52a2adeU, 0x029fd81cU, 0x987ce701U, 0x27fefd77U, 0xdb46c66fU, 0x5d301900U,
|
||||
0x496998c0U, 0xbb6598b9U, 0x5eebb607U, 0xe547354aU, 0xdf4a2f7eU, 0xf06c4955U,
|
||||
0x96242ffaU, 0x1775fb27U, 0xbecc58ceU, 0xebf2a53bU, 0x3eaad82aU, 0xf41137baU,
|
||||
0x573e6fbaU, 0xfb4866b8U, 0x54002148U, 0x00000039U,
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
// Returns a pointer to the big integer data for (5**27)**i. i must be
|
||||
// between 1 and 20, inclusive.
|
||||
const uint32_t* LargePowerOfFiveData(int i) {
|
||||
return kLargePowersOfFive + i * (i - 1);
|
||||
}
|
||||
|
||||
// Returns the size of the big integer data for (5**27)**i, in words. i must be
|
||||
// between 1 and 20, inclusive.
|
||||
int LargePowerOfFiveSize(int i) { return 2 * i; }
|
||||
} // namespace
|
||||
|
||||
ABSL_DLL const uint32_t kFiveToNth[14] = {
|
||||
1, 5, 25, 125, 625, 3125, 15625,
|
||||
78125, 390625, 1953125, 9765625, 48828125, 244140625, 1220703125,
|
||||
};
|
||||
|
||||
ABSL_DLL const uint32_t kTenToNth[10] = {
|
||||
1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000,
|
||||
};
|
||||
|
||||
template <int max_words>
|
||||
int BigUnsigned<max_words>::ReadFloatMantissa(const ParsedFloat& fp,
|
||||
int significant_digits) {
|
||||
SetToZero();
|
||||
assert(fp.type == FloatType::kNumber);
|
||||
|
||||
if (fp.subrange_begin == nullptr) {
|
||||
// We already exactly parsed the mantissa, so no more work is necessary.
|
||||
words_[0] = fp.mantissa & 0xffffffffu;
|
||||
words_[1] = fp.mantissa >> 32;
|
||||
if (words_[1]) {
|
||||
size_ = 2;
|
||||
} else if (words_[0]) {
|
||||
size_ = 1;
|
||||
}
|
||||
return fp.exponent;
|
||||
}
|
||||
int exponent_adjust =
|
||||
ReadDigits(fp.subrange_begin, fp.subrange_end, significant_digits);
|
||||
return fp.literal_exponent + exponent_adjust;
|
||||
}
|
||||
|
||||
template <int max_words>
|
||||
int BigUnsigned<max_words>::ReadDigits(const char* begin, const char* end,
|
||||
int significant_digits) {
|
||||
assert(significant_digits <= Digits10() + 1);
|
||||
SetToZero();
|
||||
|
||||
bool after_decimal_point = false;
|
||||
// Discard any leading zeroes before the decimal point
|
||||
while (begin < end && *begin == '0') {
|
||||
++begin;
|
||||
}
|
||||
int dropped_digits = 0;
|
||||
// Discard any trailing zeroes. These may or may not be after the decimal
|
||||
// point.
|
||||
while (begin < end && *std::prev(end) == '0') {
|
||||
--end;
|
||||
++dropped_digits;
|
||||
}
|
||||
if (begin < end && *std::prev(end) == '.') {
|
||||
// If the string ends in '.', either before or after dropping zeroes, then
|
||||
// drop the decimal point and look for more digits to drop.
|
||||
dropped_digits = 0;
|
||||
--end;
|
||||
while (begin < end && *std::prev(end) == '0') {
|
||||
--end;
|
||||
++dropped_digits;
|
||||
}
|
||||
} else if (dropped_digits) {
|
||||
// We dropped digits, and aren't sure if they're before or after the decimal
|
||||
// point. Figure that out now.
|
||||
const char* dp = std::find(begin, end, '.');
|
||||
if (dp != end) {
|
||||
// The dropped trailing digits were after the decimal point, so don't
|
||||
// count them.
|
||||
dropped_digits = 0;
|
||||
}
|
||||
}
|
||||
// Any non-fraction digits we dropped need to be accounted for in our exponent
|
||||
// adjustment.
|
||||
int exponent_adjust = dropped_digits;
|
||||
|
||||
uint32_t queued = 0;
|
||||
int digits_queued = 0;
|
||||
for (; begin != end && significant_digits > 0; ++begin) {
|
||||
if (*begin == '.') {
|
||||
after_decimal_point = true;
|
||||
continue;
|
||||
}
|
||||
if (after_decimal_point) {
|
||||
// For each fractional digit we emit in our parsed integer, adjust our
|
||||
// decimal exponent to compensate.
|
||||
--exponent_adjust;
|
||||
}
|
||||
char digit = (*begin - '0');
|
||||
--significant_digits;
|
||||
if (significant_digits == 0 && std::next(begin) != end &&
|
||||
(digit == 0 || digit == 5)) {
|
||||
// If this is the very last significant digit, but insignificant digits
|
||||
// remain, we know that the last of those remaining significant digits is
|
||||
// nonzero. (If it wasn't, we would have stripped it before we got here.)
|
||||
// So if this final digit is a 0 or 5, adjust it upward by 1.
|
||||
//
|
||||
// This adjustment is what allows incredibly large mantissas ending in
|
||||
// 500000...000000000001 to correctly round up, rather than to nearest.
|
||||
++digit;
|
||||
}
|
||||
queued = 10 * queued + static_cast<uint32_t>(digit);
|
||||
++digits_queued;
|
||||
if (digits_queued == kMaxSmallPowerOfTen) {
|
||||
MultiplyBy(kTenToNth[kMaxSmallPowerOfTen]);
|
||||
AddWithCarry(0, queued);
|
||||
queued = digits_queued = 0;
|
||||
}
|
||||
}
|
||||
// Encode any remaining digits.
|
||||
if (digits_queued) {
|
||||
MultiplyBy(kTenToNth[digits_queued]);
|
||||
AddWithCarry(0, queued);
|
||||
}
|
||||
|
||||
// If any insignificant digits remain, we will drop them. But if we have not
|
||||
// yet read the decimal point, then we have to adjust the exponent to account
|
||||
// for the dropped digits.
|
||||
if (begin < end && !after_decimal_point) {
|
||||
// This call to std::find will result in a pointer either to the decimal
|
||||
// point, or to the end of our buffer if there was none.
|
||||
//
|
||||
// Either way, [begin, decimal_point) will contain the set of dropped digits
|
||||
// that require an exponent adjustment.
|
||||
const char* decimal_point = std::find(begin, end, '.');
|
||||
exponent_adjust += (decimal_point - begin);
|
||||
}
|
||||
return exponent_adjust;
|
||||
}
|
||||
|
||||
template <int max_words>
|
||||
/* static */ BigUnsigned<max_words> BigUnsigned<max_words>::FiveToTheNth(
|
||||
int n) {
|
||||
BigUnsigned answer(1u);
|
||||
|
||||
// Seed from the table of large powers, if possible.
|
||||
bool first_pass = true;
|
||||
while (n >= kLargePowerOfFiveStep) {
|
||||
int big_power =
|
||||
std::min(n / kLargePowerOfFiveStep, kLargestPowerOfFiveIndex);
|
||||
if (first_pass) {
|
||||
// just copy, rather than multiplying by 1
|
||||
std::copy_n(LargePowerOfFiveData(big_power),
|
||||
LargePowerOfFiveSize(big_power), answer.words_);
|
||||
answer.size_ = LargePowerOfFiveSize(big_power);
|
||||
first_pass = false;
|
||||
} else {
|
||||
answer.MultiplyBy(LargePowerOfFiveSize(big_power),
|
||||
LargePowerOfFiveData(big_power));
|
||||
}
|
||||
n -= kLargePowerOfFiveStep * big_power;
|
||||
}
|
||||
answer.MultiplyByFiveToTheNth(n);
|
||||
return answer;
|
||||
}
|
||||
|
||||
template <int max_words>
|
||||
void BigUnsigned<max_words>::MultiplyStep(int original_size,
|
||||
const uint32_t* other_words,
|
||||
int other_size, int step) {
|
||||
int this_i = std::min(original_size - 1, step);
|
||||
int other_i = step - this_i;
|
||||
|
||||
uint64_t this_word = 0;
|
||||
uint64_t carry = 0;
|
||||
for (; this_i >= 0 && other_i < other_size; --this_i, ++other_i) {
|
||||
uint64_t product = words_[this_i];
|
||||
product *= other_words[other_i];
|
||||
this_word += product;
|
||||
carry += (this_word >> 32);
|
||||
this_word &= 0xffffffff;
|
||||
}
|
||||
AddWithCarry(step + 1, carry);
|
||||
words_[step] = this_word & 0xffffffff;
|
||||
if (this_word > 0 && size_ <= step) {
|
||||
size_ = step + 1;
|
||||
}
|
||||
}
|
||||
|
||||
template <int max_words>
|
||||
std::string BigUnsigned<max_words>::ToString() const {
|
||||
BigUnsigned<max_words> copy = *this;
|
||||
std::string result;
|
||||
// Build result in reverse order
|
||||
while (copy.size() > 0) {
|
||||
uint32_t next_digit = copy.DivMod<10>();
|
||||
result.push_back('0' + static_cast<char>(next_digit));
|
||||
}
|
||||
if (result.empty()) {
|
||||
result.push_back('0');
|
||||
}
|
||||
std::reverse(result.begin(), result.end());
|
||||
return result;
|
||||
}
|
||||
|
||||
template class BigUnsigned<4>;
|
||||
template class BigUnsigned<84>;
|
||||
|
||||
} // namespace strings_internal
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
423
Pods/abseil/absl/strings/internal/charconv_bigint.h
generated
Normal file
423
Pods/abseil/absl/strings/internal/charconv_bigint.h
generated
Normal file
@@ -0,0 +1,423 @@
|
||||
// Copyright 2018 The Abseil Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// 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_CHARCONV_BIGINT_H_
|
||||
#define ABSL_STRINGS_INTERNAL_CHARCONV_BIGINT_H_
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include "absl/base/config.h"
|
||||
#include "absl/strings/ascii.h"
|
||||
#include "absl/strings/internal/charconv_parse.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace strings_internal {
|
||||
|
||||
// The largest power that 5 that can be raised to, and still fit in a uint32_t.
|
||||
constexpr int kMaxSmallPowerOfFive = 13;
|
||||
// The largest power that 10 that can be raised to, and still fit in a uint32_t.
|
||||
constexpr int kMaxSmallPowerOfTen = 9;
|
||||
|
||||
ABSL_DLL extern const uint32_t
|
||||
kFiveToNth[kMaxSmallPowerOfFive + 1];
|
||||
ABSL_DLL extern const uint32_t kTenToNth[kMaxSmallPowerOfTen + 1];
|
||||
|
||||
// Large, fixed-width unsigned integer.
|
||||
//
|
||||
// Exact rounding for decimal-to-binary floating point conversion requires very
|
||||
// large integer math, but a design goal of absl::from_chars is to avoid
|
||||
// allocating memory. The integer precision needed for decimal-to-binary
|
||||
// conversions is large but bounded, so a huge fixed-width integer class
|
||||
// suffices.
|
||||
//
|
||||
// This is an intentionally limited big integer class. Only needed operations
|
||||
// are implemented. All storage lives in an array data member, and all
|
||||
// arithmetic is done in-place, to avoid requiring separate storage for operand
|
||||
// and result.
|
||||
//
|
||||
// This is an internal class. Some methods live in the .cc file, and are
|
||||
// instantiated only for the values of max_words we need.
|
||||
template <int max_words>
|
||||
class BigUnsigned {
|
||||
public:
|
||||
static_assert(max_words == 4 || max_words == 84,
|
||||
"unsupported max_words value");
|
||||
|
||||
BigUnsigned() : size_(0), words_{} {}
|
||||
explicit constexpr BigUnsigned(uint64_t v)
|
||||
: size_((v >> 32) ? 2 : v ? 1 : 0),
|
||||
words_{static_cast<uint32_t>(v & 0xffffffffu),
|
||||
static_cast<uint32_t>(v >> 32)} {}
|
||||
|
||||
// Constructs a BigUnsigned from the given string_view containing a decimal
|
||||
// value. If the input string is not a decimal integer, constructs a 0
|
||||
// instead.
|
||||
explicit BigUnsigned(absl::string_view sv) : size_(0), words_{} {
|
||||
// Check for valid input, returning a 0 otherwise. This is reasonable
|
||||
// behavior only because this constructor is for unit tests.
|
||||
if (std::find_if_not(sv.begin(), sv.end(), ascii_isdigit) != sv.end() ||
|
||||
sv.empty()) {
|
||||
return;
|
||||
}
|
||||
int exponent_adjust =
|
||||
ReadDigits(sv.data(), sv.data() + sv.size(), Digits10() + 1);
|
||||
if (exponent_adjust > 0) {
|
||||
MultiplyByTenToTheNth(exponent_adjust);
|
||||
}
|
||||
}
|
||||
|
||||
// Loads the mantissa value of a previously-parsed float.
|
||||
//
|
||||
// Returns the associated decimal exponent. The value of the parsed float is
|
||||
// exactly *this * 10**exponent.
|
||||
int ReadFloatMantissa(const ParsedFloat& fp, int significant_digits);
|
||||
|
||||
// Returns the number of decimal digits of precision this type provides. All
|
||||
// numbers with this many decimal digits or fewer are representable by this
|
||||
// type.
|
||||
//
|
||||
// Analogous to std::numeric_limits<BigUnsigned>::digits10.
|
||||
static constexpr int Digits10() {
|
||||
// 9975007/1035508 is very slightly less than log10(2**32).
|
||||
return static_cast<uint64_t>(max_words) * 9975007 / 1035508;
|
||||
}
|
||||
|
||||
// Shifts left by the given number of bits.
|
||||
void ShiftLeft(int count) {
|
||||
if (count > 0) {
|
||||
const int word_shift = count / 32;
|
||||
if (word_shift >= max_words) {
|
||||
SetToZero();
|
||||
return;
|
||||
}
|
||||
size_ = (std::min)(size_ + word_shift, max_words);
|
||||
count %= 32;
|
||||
if (count == 0) {
|
||||
std::copy_backward(words_, words_ + size_ - word_shift, words_ + size_);
|
||||
} else {
|
||||
for (int i = (std::min)(size_, max_words - 1); i > word_shift; --i) {
|
||||
words_[i] = (words_[i - word_shift] << count) |
|
||||
(words_[i - word_shift - 1] >> (32 - count));
|
||||
}
|
||||
words_[word_shift] = words_[0] << count;
|
||||
// Grow size_ if necessary.
|
||||
if (size_ < max_words && words_[size_]) {
|
||||
++size_;
|
||||
}
|
||||
}
|
||||
std::fill_n(words_, word_shift, 0u);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Multiplies by v in-place.
|
||||
void MultiplyBy(uint32_t v) {
|
||||
if (size_ == 0 || v == 1) {
|
||||
return;
|
||||
}
|
||||
if (v == 0) {
|
||||
SetToZero();
|
||||
return;
|
||||
}
|
||||
const uint64_t factor = v;
|
||||
uint64_t window = 0;
|
||||
for (int i = 0; i < size_; ++i) {
|
||||
window += factor * words_[i];
|
||||
words_[i] = window & 0xffffffff;
|
||||
window >>= 32;
|
||||
}
|
||||
// If carry bits remain and there's space for them, grow size_.
|
||||
if (window && size_ < max_words) {
|
||||
words_[size_] = window & 0xffffffff;
|
||||
++size_;
|
||||
}
|
||||
}
|
||||
|
||||
void MultiplyBy(uint64_t v) {
|
||||
uint32_t words[2];
|
||||
words[0] = static_cast<uint32_t>(v);
|
||||
words[1] = static_cast<uint32_t>(v >> 32);
|
||||
if (words[1] == 0) {
|
||||
MultiplyBy(words[0]);
|
||||
} else {
|
||||
MultiplyBy(2, words);
|
||||
}
|
||||
}
|
||||
|
||||
// Multiplies in place by 5 to the power of n. n must be non-negative.
|
||||
void MultiplyByFiveToTheNth(int n) {
|
||||
while (n >= kMaxSmallPowerOfFive) {
|
||||
MultiplyBy(kFiveToNth[kMaxSmallPowerOfFive]);
|
||||
n -= kMaxSmallPowerOfFive;
|
||||
}
|
||||
if (n > 0) {
|
||||
MultiplyBy(kFiveToNth[n]);
|
||||
}
|
||||
}
|
||||
|
||||
// Multiplies in place by 10 to the power of n. n must be non-negative.
|
||||
void MultiplyByTenToTheNth(int n) {
|
||||
if (n > kMaxSmallPowerOfTen) {
|
||||
// For large n, raise to a power of 5, then shift left by the same amount.
|
||||
// (10**n == 5**n * 2**n.) This requires fewer multiplications overall.
|
||||
MultiplyByFiveToTheNth(n);
|
||||
ShiftLeft(n);
|
||||
} else if (n > 0) {
|
||||
// We can do this more quickly for very small N by using a single
|
||||
// multiplication.
|
||||
MultiplyBy(kTenToNth[n]);
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the value of 5**n, for non-negative n. This implementation uses
|
||||
// a lookup table, and is faster then seeding a BigUnsigned with 1 and calling
|
||||
// MultiplyByFiveToTheNth().
|
||||
static BigUnsigned FiveToTheNth(int n);
|
||||
|
||||
// Multiplies by another BigUnsigned, in-place.
|
||||
template <int M>
|
||||
void MultiplyBy(const BigUnsigned<M>& other) {
|
||||
MultiplyBy(other.size(), other.words());
|
||||
}
|
||||
|
||||
void SetToZero() {
|
||||
std::fill_n(words_, size_, 0u);
|
||||
size_ = 0;
|
||||
}
|
||||
|
||||
// Returns the value of the nth word of this BigUnsigned. This is
|
||||
// range-checked, and returns 0 on out-of-bounds accesses.
|
||||
uint32_t GetWord(int index) const {
|
||||
if (index < 0 || index >= size_) {
|
||||
return 0;
|
||||
}
|
||||
return words_[index];
|
||||
}
|
||||
|
||||
// Returns this integer as a decimal string. This is not used in the decimal-
|
||||
// to-binary conversion; it is intended to aid in testing.
|
||||
std::string ToString() const;
|
||||
|
||||
int size() const { return size_; }
|
||||
const uint32_t* words() const { return words_; }
|
||||
|
||||
private:
|
||||
// Reads the number between [begin, end), possibly containing a decimal point,
|
||||
// into this BigUnsigned.
|
||||
//
|
||||
// Callers are required to ensure [begin, end) contains a valid number, with
|
||||
// one or more decimal digits and at most one decimal point. This routine
|
||||
// will behave unpredictably if these preconditions are not met.
|
||||
//
|
||||
// Only the first `significant_digits` digits are read. Digits beyond this
|
||||
// limit are "sticky": If the final significant digit is 0 or 5, and if any
|
||||
// dropped digit is nonzero, then that final significant digit is adjusted up
|
||||
// to 1 or 6. This adjustment allows for precise rounding.
|
||||
//
|
||||
// Returns `exponent_adjustment`, a power-of-ten exponent adjustment to
|
||||
// account for the decimal point and for dropped significant digits. After
|
||||
// this function returns,
|
||||
// actual_value_of_parsed_string ~= *this * 10**exponent_adjustment.
|
||||
int ReadDigits(const char* begin, const char* end, int significant_digits);
|
||||
|
||||
// Performs a step of big integer multiplication. This computes the full
|
||||
// (64-bit-wide) values that should be added at the given index (step), and
|
||||
// adds to that location in-place.
|
||||
//
|
||||
// Because our math all occurs in place, we must multiply starting from the
|
||||
// highest word working downward. (This is a bit more expensive due to the
|
||||
// extra carries involved.)
|
||||
//
|
||||
// This must be called in steps, for each word to be calculated, starting from
|
||||
// the high end and working down to 0. The first value of `step` should be
|
||||
// `std::min(original_size + other.size_ - 2, max_words - 1)`.
|
||||
// The reason for this expression is that multiplying the i'th word from one
|
||||
// multiplicand and the j'th word of another multiplicand creates a
|
||||
// two-word-wide value to be stored at the (i+j)'th element. The highest
|
||||
// word indices we will access are `original_size - 1` from this object, and
|
||||
// `other.size_ - 1` from our operand. Therefore,
|
||||
// `original_size + other.size_ - 2` is the first step we should calculate,
|
||||
// but limited on an upper bound by max_words.
|
||||
|
||||
// Working from high-to-low ensures that we do not overwrite the portions of
|
||||
// the initial value of *this which are still needed for later steps.
|
||||
//
|
||||
// Once called with step == 0, *this contains the result of the
|
||||
// multiplication.
|
||||
//
|
||||
// `original_size` is the size_ of *this before the first call to
|
||||
// MultiplyStep(). `other_words` and `other_size` are the contents of our
|
||||
// operand. `step` is the step to perform, as described above.
|
||||
void MultiplyStep(int original_size, const uint32_t* other_words,
|
||||
int other_size, int step);
|
||||
|
||||
void MultiplyBy(int other_size, const uint32_t* other_words) {
|
||||
const int original_size = size_;
|
||||
const int first_step =
|
||||
(std::min)(original_size + other_size - 2, max_words - 1);
|
||||
for (int step = first_step; step >= 0; --step) {
|
||||
MultiplyStep(original_size, other_words, other_size, step);
|
||||
}
|
||||
}
|
||||
|
||||
// Adds a 32-bit value to the index'th word, with carry.
|
||||
void AddWithCarry(int index, uint32_t value) {
|
||||
if (value) {
|
||||
while (index < max_words && value > 0) {
|
||||
words_[index] += value;
|
||||
// carry if we overflowed in this word:
|
||||
if (value > words_[index]) {
|
||||
value = 1;
|
||||
++index;
|
||||
} else {
|
||||
value = 0;
|
||||
}
|
||||
}
|
||||
size_ = (std::min)(max_words, (std::max)(index + 1, size_));
|
||||
}
|
||||
}
|
||||
|
||||
void AddWithCarry(int index, uint64_t value) {
|
||||
if (value && index < max_words) {
|
||||
uint32_t high = value >> 32;
|
||||
uint32_t low = value & 0xffffffff;
|
||||
words_[index] += low;
|
||||
if (words_[index] < low) {
|
||||
++high;
|
||||
if (high == 0) {
|
||||
// Carry from the low word caused our high word to overflow.
|
||||
// Short circuit here to do the right thing.
|
||||
AddWithCarry(index + 2, static_cast<uint32_t>(1));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (high > 0) {
|
||||
AddWithCarry(index + 1, high);
|
||||
} else {
|
||||
// Normally 32-bit AddWithCarry() sets size_, but since we don't call
|
||||
// it when `high` is 0, do it ourselves here.
|
||||
size_ = (std::min)(max_words, (std::max)(index + 1, size_));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Divide this in place by a constant divisor. Returns the remainder of the
|
||||
// division.
|
||||
template <uint32_t divisor>
|
||||
uint32_t DivMod() {
|
||||
uint64_t accumulator = 0;
|
||||
for (int i = size_ - 1; i >= 0; --i) {
|
||||
accumulator <<= 32;
|
||||
accumulator += words_[i];
|
||||
// accumulator / divisor will never overflow an int32_t in this loop
|
||||
words_[i] = static_cast<uint32_t>(accumulator / divisor);
|
||||
accumulator = accumulator % divisor;
|
||||
}
|
||||
while (size_ > 0 && words_[size_ - 1] == 0) {
|
||||
--size_;
|
||||
}
|
||||
return static_cast<uint32_t>(accumulator);
|
||||
}
|
||||
|
||||
// The number of elements in words_ that may carry significant values.
|
||||
// All elements beyond this point are 0.
|
||||
//
|
||||
// When size_ is 0, this BigUnsigned stores the value 0.
|
||||
// When size_ is nonzero, is *not* guaranteed that words_[size_ - 1] is
|
||||
// nonzero. This can occur due to overflow truncation.
|
||||
// In particular, x.size_ != y.size_ does *not* imply x != y.
|
||||
int size_;
|
||||
uint32_t words_[max_words];
|
||||
};
|
||||
|
||||
// Compares two big integer instances.
|
||||
//
|
||||
// Returns -1 if lhs < rhs, 0 if lhs == rhs, and 1 if lhs > rhs.
|
||||
template <int N, int M>
|
||||
int Compare(const BigUnsigned<N>& lhs, const BigUnsigned<M>& rhs) {
|
||||
int limit = (std::max)(lhs.size(), rhs.size());
|
||||
for (int i = limit - 1; i >= 0; --i) {
|
||||
const uint32_t lhs_word = lhs.GetWord(i);
|
||||
const uint32_t rhs_word = rhs.GetWord(i);
|
||||
if (lhs_word < rhs_word) {
|
||||
return -1;
|
||||
} else if (lhs_word > rhs_word) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <int N, int M>
|
||||
bool operator==(const BigUnsigned<N>& lhs, const BigUnsigned<M>& rhs) {
|
||||
int limit = (std::max)(lhs.size(), rhs.size());
|
||||
for (int i = 0; i < limit; ++i) {
|
||||
if (lhs.GetWord(i) != rhs.GetWord(i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <int N, int M>
|
||||
bool operator!=(const BigUnsigned<N>& lhs, const BigUnsigned<M>& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
template <int N, int M>
|
||||
bool operator<(const BigUnsigned<N>& lhs, const BigUnsigned<M>& rhs) {
|
||||
return Compare(lhs, rhs) == -1;
|
||||
}
|
||||
|
||||
template <int N, int M>
|
||||
bool operator>(const BigUnsigned<N>& lhs, const BigUnsigned<M>& rhs) {
|
||||
return rhs < lhs;
|
||||
}
|
||||
template <int N, int M>
|
||||
bool operator<=(const BigUnsigned<N>& lhs, const BigUnsigned<M>& rhs) {
|
||||
return !(rhs < lhs);
|
||||
}
|
||||
template <int N, int M>
|
||||
bool operator>=(const BigUnsigned<N>& lhs, const BigUnsigned<M>& rhs) {
|
||||
return !(lhs < rhs);
|
||||
}
|
||||
|
||||
// Output operator for BigUnsigned, for testing purposes only.
|
||||
template <int N>
|
||||
std::ostream& operator<<(std::ostream& os, const BigUnsigned<N>& num) {
|
||||
return os << num.ToString();
|
||||
}
|
||||
|
||||
// Explicit instantiation declarations for the sizes of BigUnsigned that we
|
||||
// are using.
|
||||
//
|
||||
// For now, the choices of 4 and 84 are arbitrary; 4 is a small value that is
|
||||
// still bigger than an int128, and 84 is a large value we will want to use
|
||||
// in the from_chars implementation.
|
||||
//
|
||||
// Comments justifying the use of 84 belong in the from_chars implementation,
|
||||
// and will be added in a follow-up CL.
|
||||
extern template class BigUnsigned<4>;
|
||||
extern template class BigUnsigned<84>;
|
||||
|
||||
} // namespace strings_internal
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
|
||||
#endif // ABSL_STRINGS_INTERNAL_CHARCONV_BIGINT_H_
|
||||
504
Pods/abseil/absl/strings/internal/charconv_parse.cc
generated
Normal file
504
Pods/abseil/absl/strings/internal/charconv_parse.cc
generated
Normal file
@@ -0,0 +1,504 @@
|
||||
// Copyright 2018 The Abseil Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// 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/charconv_parse.h"
|
||||
#include "absl/strings/charconv.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
|
||||
#include "absl/strings/internal/memutil.h"
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace {
|
||||
|
||||
// ParseFloat<10> will read the first 19 significant digits of the mantissa.
|
||||
// This number was chosen for multiple reasons.
|
||||
//
|
||||
// (a) First, for whatever integer type we choose to represent the mantissa, we
|
||||
// want to choose the largest possible number of decimal digits for that integer
|
||||
// type. We are using uint64_t, which can express any 19-digit unsigned
|
||||
// integer.
|
||||
//
|
||||
// (b) Second, we need to parse enough digits that the binary value of any
|
||||
// mantissa we capture has more bits of resolution than the mantissa
|
||||
// representation in the target float. Our algorithm requires at least 3 bits
|
||||
// of headway, but 19 decimal digits give a little more than that.
|
||||
//
|
||||
// The following static assertions verify the above comments:
|
||||
constexpr int kDecimalMantissaDigitsMax = 19;
|
||||
|
||||
static_assert(std::numeric_limits<uint64_t>::digits10 ==
|
||||
kDecimalMantissaDigitsMax,
|
||||
"(a) above");
|
||||
|
||||
// IEEE doubles, which we assume in Abseil, have 53 binary bits of mantissa.
|
||||
static_assert(std::numeric_limits<double>::is_iec559, "IEEE double assumed");
|
||||
static_assert(std::numeric_limits<double>::radix == 2, "IEEE double fact");
|
||||
static_assert(std::numeric_limits<double>::digits == 53, "IEEE double fact");
|
||||
|
||||
// The lowest valued 19-digit decimal mantissa we can read still contains
|
||||
// sufficient information to reconstruct a binary mantissa.
|
||||
static_assert(1000000000000000000u > (uint64_t{1} << (53 + 3)), "(b) above");
|
||||
|
||||
// ParseFloat<16> will read the first 15 significant digits of the mantissa.
|
||||
//
|
||||
// Because a base-16-to-base-2 conversion can be done exactly, we do not need
|
||||
// to maximize the number of scanned hex digits to improve our conversion. What
|
||||
// is required is to scan two more bits than the mantissa can represent, so that
|
||||
// we always round correctly.
|
||||
//
|
||||
// (One extra bit does not suffice to perform correct rounding, since a number
|
||||
// exactly halfway between two representable floats has unique rounding rules,
|
||||
// so we need to differentiate between a "halfway between" number and a "closer
|
||||
// to the larger value" number.)
|
||||
constexpr int kHexadecimalMantissaDigitsMax = 15;
|
||||
|
||||
// The minimum number of significant bits that will be read from
|
||||
// kHexadecimalMantissaDigitsMax hex digits. We must subtract by three, since
|
||||
// the most significant digit can be a "1", which only contributes a single
|
||||
// significant bit.
|
||||
constexpr int kGuaranteedHexadecimalMantissaBitPrecision =
|
||||
4 * kHexadecimalMantissaDigitsMax - 3;
|
||||
|
||||
static_assert(kGuaranteedHexadecimalMantissaBitPrecision >
|
||||
std::numeric_limits<double>::digits + 2,
|
||||
"kHexadecimalMantissaDigitsMax too small");
|
||||
|
||||
// We also impose a limit on the number of significant digits we will read from
|
||||
// an exponent, to avoid having to deal with integer overflow. We use 9 for
|
||||
// this purpose.
|
||||
//
|
||||
// If we read a 9 digit exponent, the end result of the conversion will
|
||||
// necessarily be infinity or zero, depending on the sign of the exponent.
|
||||
// Therefore we can just drop extra digits on the floor without any extra
|
||||
// logic.
|
||||
constexpr int kDecimalExponentDigitsMax = 9;
|
||||
static_assert(std::numeric_limits<int>::digits10 >= kDecimalExponentDigitsMax,
|
||||
"int type too small");
|
||||
|
||||
// To avoid incredibly large inputs causing integer overflow for our exponent,
|
||||
// we impose an arbitrary but very large limit on the number of significant
|
||||
// digits we will accept. The implementation refuses to match a string with
|
||||
// more consecutive significant mantissa digits than this.
|
||||
constexpr int kDecimalDigitLimit = 50000000;
|
||||
|
||||
// Corresponding limit for hexadecimal digit inputs. This is one fourth the
|
||||
// amount of kDecimalDigitLimit, since each dropped hexadecimal digit requires
|
||||
// a binary exponent adjustment of 4.
|
||||
constexpr int kHexadecimalDigitLimit = kDecimalDigitLimit / 4;
|
||||
|
||||
// The largest exponent we can read is 999999999 (per
|
||||
// kDecimalExponentDigitsMax), and the largest exponent adjustment we can get
|
||||
// from dropped mantissa digits is 2 * kDecimalDigitLimit, and the sum of these
|
||||
// comfortably fits in an integer.
|
||||
//
|
||||
// We count kDecimalDigitLimit twice because there are independent limits for
|
||||
// numbers before and after the decimal point. (In the case where there are no
|
||||
// significant digits before the decimal point, there are independent limits for
|
||||
// post-decimal-point leading zeroes and for significant digits.)
|
||||
static_assert(999999999 + 2 * kDecimalDigitLimit <
|
||||
std::numeric_limits<int>::max(),
|
||||
"int type too small");
|
||||
static_assert(999999999 + 2 * (4 * kHexadecimalDigitLimit) <
|
||||
std::numeric_limits<int>::max(),
|
||||
"int type too small");
|
||||
|
||||
// Returns true if the provided bitfield allows parsing an exponent value
|
||||
// (e.g., "1.5e100").
|
||||
bool AllowExponent(chars_format flags) {
|
||||
bool fixed = (flags & chars_format::fixed) == chars_format::fixed;
|
||||
bool scientific =
|
||||
(flags & chars_format::scientific) == chars_format::scientific;
|
||||
return scientific || !fixed;
|
||||
}
|
||||
|
||||
// Returns true if the provided bitfield requires an exponent value be present.
|
||||
bool RequireExponent(chars_format flags) {
|
||||
bool fixed = (flags & chars_format::fixed) == chars_format::fixed;
|
||||
bool scientific =
|
||||
(flags & chars_format::scientific) == chars_format::scientific;
|
||||
return scientific && !fixed;
|
||||
}
|
||||
|
||||
const int8_t kAsciiToInt[256] = {
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8,
|
||||
9, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1};
|
||||
|
||||
// Returns true if `ch` is a digit in the given base
|
||||
template <int base>
|
||||
bool IsDigit(char ch);
|
||||
|
||||
// Converts a valid `ch` to its digit value in the given base.
|
||||
template <int base>
|
||||
unsigned ToDigit(char ch);
|
||||
|
||||
// Returns true if `ch` is the exponent delimiter for the given base.
|
||||
template <int base>
|
||||
bool IsExponentCharacter(char ch);
|
||||
|
||||
// Returns the maximum number of significant digits we will read for a float
|
||||
// in the given base.
|
||||
template <int base>
|
||||
constexpr int MantissaDigitsMax();
|
||||
|
||||
// Returns the largest consecutive run of digits we will accept when parsing a
|
||||
// number in the given base.
|
||||
template <int base>
|
||||
constexpr int DigitLimit();
|
||||
|
||||
// Returns the amount the exponent must be adjusted by for each dropped digit.
|
||||
// (For decimal this is 1, since the digits are in base 10 and the exponent base
|
||||
// is also 10, but for hexadecimal this is 4, since the digits are base 16 but
|
||||
// the exponent base is 2.)
|
||||
template <int base>
|
||||
constexpr int DigitMagnitude();
|
||||
|
||||
template <>
|
||||
bool IsDigit<10>(char ch) {
|
||||
return ch >= '0' && ch <= '9';
|
||||
}
|
||||
template <>
|
||||
bool IsDigit<16>(char ch) {
|
||||
return kAsciiToInt[static_cast<unsigned char>(ch)] >= 0;
|
||||
}
|
||||
|
||||
template <>
|
||||
unsigned ToDigit<10>(char ch) {
|
||||
return static_cast<unsigned>(ch - '0');
|
||||
}
|
||||
template <>
|
||||
unsigned ToDigit<16>(char ch) {
|
||||
return static_cast<unsigned>(kAsciiToInt[static_cast<unsigned char>(ch)]);
|
||||
}
|
||||
|
||||
template <>
|
||||
bool IsExponentCharacter<10>(char ch) {
|
||||
return ch == 'e' || ch == 'E';
|
||||
}
|
||||
|
||||
template <>
|
||||
bool IsExponentCharacter<16>(char ch) {
|
||||
return ch == 'p' || ch == 'P';
|
||||
}
|
||||
|
||||
template <>
|
||||
constexpr int MantissaDigitsMax<10>() {
|
||||
return kDecimalMantissaDigitsMax;
|
||||
}
|
||||
template <>
|
||||
constexpr int MantissaDigitsMax<16>() {
|
||||
return kHexadecimalMantissaDigitsMax;
|
||||
}
|
||||
|
||||
template <>
|
||||
constexpr int DigitLimit<10>() {
|
||||
return kDecimalDigitLimit;
|
||||
}
|
||||
template <>
|
||||
constexpr int DigitLimit<16>() {
|
||||
return kHexadecimalDigitLimit;
|
||||
}
|
||||
|
||||
template <>
|
||||
constexpr int DigitMagnitude<10>() {
|
||||
return 1;
|
||||
}
|
||||
template <>
|
||||
constexpr int DigitMagnitude<16>() {
|
||||
return 4;
|
||||
}
|
||||
|
||||
// Reads decimal digits from [begin, end) into *out. Returns the number of
|
||||
// digits consumed.
|
||||
//
|
||||
// After max_digits has been read, keeps consuming characters, but no longer
|
||||
// adjusts *out. If a nonzero digit is dropped this way, *dropped_nonzero_digit
|
||||
// is set; otherwise, it is left unmodified.
|
||||
//
|
||||
// If no digits are matched, returns 0 and leaves *out unchanged.
|
||||
//
|
||||
// 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>
|
||||
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) {
|
||||
assert(max_digits * 4 <= std::numeric_limits<T>::digits);
|
||||
}
|
||||
const char* const original_begin = begin;
|
||||
|
||||
// Skip leading zeros, but only if *out is zero.
|
||||
// They don't cause an overflow so we don't have to count them for
|
||||
// `max_digits`.
|
||||
while (!*out && end != begin && *begin == '0') ++begin;
|
||||
|
||||
T accumulator = *out;
|
||||
const char* significant_digits_end =
|
||||
(end - begin > max_digits) ? begin + max_digits : end;
|
||||
while (begin < significant_digits_end && IsDigit<base>(*begin)) {
|
||||
// Do not guard against *out overflow; max_digits was chosen to avoid this.
|
||||
// Do assert against it, to detect problems in debug builds.
|
||||
auto digit = static_cast<T>(ToDigit<base>(*begin));
|
||||
assert(accumulator * base >= accumulator);
|
||||
accumulator *= base;
|
||||
assert(accumulator + digit >= accumulator);
|
||||
accumulator += digit;
|
||||
++begin;
|
||||
}
|
||||
bool dropped_nonzero = false;
|
||||
while (begin < end && IsDigit<base>(*begin)) {
|
||||
dropped_nonzero = dropped_nonzero || (*begin != '0');
|
||||
++begin;
|
||||
}
|
||||
if (dropped_nonzero && dropped_nonzero_digit != nullptr) {
|
||||
*dropped_nonzero_digit = true;
|
||||
}
|
||||
*out = accumulator;
|
||||
return static_cast<int>(begin - original_begin);
|
||||
}
|
||||
|
||||
// Returns true if `v` is one of the chars allowed inside parentheses following
|
||||
// a NaN.
|
||||
bool IsNanChar(char v) {
|
||||
return (v == '_') || (v >= '0' && v <= '9') || (v >= 'a' && v <= 'z') ||
|
||||
(v >= 'A' && v <= 'Z');
|
||||
}
|
||||
|
||||
// Checks the range [begin, end) for a strtod()-formatted infinity or NaN. If
|
||||
// one is found, sets `out` appropriately and returns true.
|
||||
bool ParseInfinityOrNan(const char* begin, const char* end,
|
||||
strings_internal::ParsedFloat* out) {
|
||||
if (end - begin < 3) {
|
||||
return false;
|
||||
}
|
||||
switch (*begin) {
|
||||
case 'i':
|
||||
case 'I': {
|
||||
// An infinity string consists of the characters "inf" or "infinity",
|
||||
// case insensitive.
|
||||
if (strings_internal::memcasecmp(begin + 1, "nf", 2) != 0) {
|
||||
return false;
|
||||
}
|
||||
out->type = strings_internal::FloatType::kInfinity;
|
||||
if (end - begin >= 8 &&
|
||||
strings_internal::memcasecmp(begin + 3, "inity", 5) == 0) {
|
||||
out->end = begin + 8;
|
||||
} else {
|
||||
out->end = begin + 3;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case 'n':
|
||||
case 'N': {
|
||||
// A NaN consists of the characters "nan", case insensitive, optionally
|
||||
// followed by a parenthesized sequence of zero or more alphanumeric
|
||||
// characters and/or underscores.
|
||||
if (strings_internal::memcasecmp(begin + 1, "an", 2) != 0) {
|
||||
return false;
|
||||
}
|
||||
out->type = strings_internal::FloatType::kNan;
|
||||
out->end = begin + 3;
|
||||
// NaN is allowed to be followed by a parenthesized string, consisting of
|
||||
// only the characters [a-zA-Z0-9_]. Match that if it's present.
|
||||
begin += 3;
|
||||
if (begin < end && *begin == '(') {
|
||||
const char* nan_begin = begin + 1;
|
||||
while (nan_begin < end && IsNanChar(*nan_begin)) {
|
||||
++nan_begin;
|
||||
}
|
||||
if (nan_begin < end && *nan_begin == ')') {
|
||||
// We found an extra NaN specifier range
|
||||
out->subrange_begin = begin + 1;
|
||||
out->subrange_end = nan_begin;
|
||||
out->end = nan_begin + 1;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace strings_internal {
|
||||
|
||||
template <int base>
|
||||
strings_internal::ParsedFloat ParseFloat(const char* begin, const char* end,
|
||||
chars_format format_flags) {
|
||||
strings_internal::ParsedFloat result;
|
||||
|
||||
// Exit early if we're given an empty range.
|
||||
if (begin == end) return result;
|
||||
|
||||
// Handle the infinity and NaN cases.
|
||||
if (ParseInfinityOrNan(begin, end, &result)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const char* const mantissa_begin = begin;
|
||||
while (begin < end && *begin == '0') {
|
||||
++begin; // skip leading zeros
|
||||
}
|
||||
uint64_t mantissa = 0;
|
||||
|
||||
int exponent_adjustment = 0;
|
||||
bool mantissa_is_inexact = false;
|
||||
int pre_decimal_digits = ConsumeDigits<base>(
|
||||
begin, end, MantissaDigitsMax<base>(), &mantissa, &mantissa_is_inexact);
|
||||
begin += pre_decimal_digits;
|
||||
int digits_left;
|
||||
if (pre_decimal_digits >= DigitLimit<base>()) {
|
||||
// refuse to parse pathological inputs
|
||||
return result;
|
||||
} else if (pre_decimal_digits > MantissaDigitsMax<base>()) {
|
||||
// We dropped some non-fraction digits on the floor. Adjust our exponent
|
||||
// to compensate.
|
||||
exponent_adjustment =
|
||||
static_cast<int>(pre_decimal_digits - MantissaDigitsMax<base>());
|
||||
digits_left = 0;
|
||||
} else {
|
||||
digits_left =
|
||||
static_cast<int>(MantissaDigitsMax<base>() - pre_decimal_digits);
|
||||
}
|
||||
if (begin < end && *begin == '.') {
|
||||
++begin;
|
||||
if (mantissa == 0) {
|
||||
// If we haven't seen any nonzero digits yet, keep skipping zeros. We
|
||||
// have to adjust the exponent to reflect the changed place value.
|
||||
const char* begin_zeros = begin;
|
||||
while (begin < end && *begin == '0') {
|
||||
++begin;
|
||||
}
|
||||
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);
|
||||
}
|
||||
int post_decimal_digits = ConsumeDigits<base>(
|
||||
begin, end, digits_left, &mantissa, &mantissa_is_inexact);
|
||||
begin += post_decimal_digits;
|
||||
|
||||
// Since `mantissa` is an integer, each significant digit we read after
|
||||
// the decimal point requires an adjustment to the exponent. "1.23e0" will
|
||||
// be stored as `mantissa` == 123 and `exponent` == -2 (that is,
|
||||
// "123e-2").
|
||||
if (post_decimal_digits >= DigitLimit<base>()) {
|
||||
// refuse to parse pathological inputs
|
||||
return result;
|
||||
} else if (post_decimal_digits > digits_left) {
|
||||
exponent_adjustment -= digits_left;
|
||||
} else {
|
||||
exponent_adjustment -= post_decimal_digits;
|
||||
}
|
||||
}
|
||||
// If we've found no mantissa whatsoever, this isn't a number.
|
||||
if (mantissa_begin == begin) {
|
||||
return result;
|
||||
}
|
||||
// A bare "." doesn't count as a mantissa either.
|
||||
if (begin - mantissa_begin == 1 && *mantissa_begin == '.') {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (mantissa_is_inexact) {
|
||||
// We dropped significant digits on the floor. Handle this appropriately.
|
||||
if (base == 10) {
|
||||
// If we truncated significant decimal digits, store the full range of the
|
||||
// mantissa for future big integer math for exact rounding.
|
||||
result.subrange_begin = mantissa_begin;
|
||||
result.subrange_end = begin;
|
||||
} else if (base == 16) {
|
||||
// If we truncated hex digits, reflect this fact by setting the low
|
||||
// ("sticky") bit. This allows for correct rounding in all cases.
|
||||
mantissa |= 1;
|
||||
}
|
||||
}
|
||||
result.mantissa = mantissa;
|
||||
|
||||
const char* const exponent_begin = begin;
|
||||
result.literal_exponent = 0;
|
||||
bool found_exponent = false;
|
||||
if (AllowExponent(format_flags) && begin < end &&
|
||||
IsExponentCharacter<base>(*begin)) {
|
||||
bool negative_exponent = false;
|
||||
++begin;
|
||||
if (begin < end && *begin == '-') {
|
||||
negative_exponent = true;
|
||||
++begin;
|
||||
} else if (begin < end && *begin == '+') {
|
||||
++begin;
|
||||
}
|
||||
const char* const exponent_digits_begin = begin;
|
||||
// Exponent is always expressed in decimal, even for hexadecimal floats.
|
||||
begin += ConsumeDigits<10>(begin, end, kDecimalExponentDigitsMax,
|
||||
&result.literal_exponent, nullptr);
|
||||
if (begin == exponent_digits_begin) {
|
||||
// there were no digits where we expected an exponent. We failed to read
|
||||
// an exponent and should not consume the 'e' after all. Rewind 'begin'.
|
||||
found_exponent = false;
|
||||
begin = exponent_begin;
|
||||
} else {
|
||||
found_exponent = true;
|
||||
if (negative_exponent) {
|
||||
result.literal_exponent = -result.literal_exponent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!found_exponent && RequireExponent(format_flags)) {
|
||||
// Provided flags required an exponent, but none was found. This results
|
||||
// in a failure to scan.
|
||||
return result;
|
||||
}
|
||||
|
||||
// Success!
|
||||
result.type = strings_internal::FloatType::kNumber;
|
||||
if (result.mantissa > 0) {
|
||||
result.exponent = result.literal_exponent +
|
||||
(DigitMagnitude<base>() * exponent_adjustment);
|
||||
} else {
|
||||
result.exponent = 0;
|
||||
}
|
||||
result.end = begin;
|
||||
return result;
|
||||
}
|
||||
|
||||
template ParsedFloat ParseFloat<10>(const char* begin, const char* end,
|
||||
chars_format format_flags);
|
||||
template ParsedFloat ParseFloat<16>(const char* begin, const char* end,
|
||||
chars_format format_flags);
|
||||
|
||||
} // namespace strings_internal
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
99
Pods/abseil/absl/strings/internal/charconv_parse.h
generated
Normal file
99
Pods/abseil/absl/strings/internal/charconv_parse.h
generated
Normal file
@@ -0,0 +1,99 @@
|
||||
// Copyright 2018 The Abseil Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// 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_CHARCONV_PARSE_H_
|
||||
#define ABSL_STRINGS_INTERNAL_CHARCONV_PARSE_H_
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "absl/base/config.h"
|
||||
#include "absl/strings/charconv.h"
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace strings_internal {
|
||||
|
||||
// Enum indicating whether a parsed float is a number or special value.
|
||||
enum class FloatType { kNumber, kInfinity, kNan };
|
||||
|
||||
// The decomposed parts of a parsed `float` or `double`.
|
||||
struct ParsedFloat {
|
||||
// Representation of the parsed mantissa, with the decimal point adjusted to
|
||||
// make it an integer.
|
||||
//
|
||||
// During decimal scanning, this contains 19 significant digits worth of
|
||||
// mantissa value. If digits beyond this point are found, they
|
||||
// are truncated, and if any of these dropped digits are nonzero, then
|
||||
// `mantissa` is inexact, and the full mantissa is stored in [subrange_begin,
|
||||
// subrange_end).
|
||||
//
|
||||
// During hexadecimal scanning, this contains 15 significant hex digits worth
|
||||
// of mantissa value. Digits beyond this point are sticky -- they are
|
||||
// truncated, but if any dropped digits are nonzero, the low bit of mantissa
|
||||
// will be set. (This allows for precise rounding, and avoids the need
|
||||
// to store the full mantissa in [subrange_begin, subrange_end).)
|
||||
uint64_t mantissa = 0;
|
||||
|
||||
// Floating point expontent. This reflects any decimal point adjustments and
|
||||
// any truncated digits from the mantissa. The absolute value of the parsed
|
||||
// number is represented by mantissa * (base ** exponent), where base==10 for
|
||||
// decimal floats, and base==2 for hexadecimal floats.
|
||||
int exponent = 0;
|
||||
|
||||
// The literal exponent value scanned from the input, or 0 if none was
|
||||
// present. This does not reflect any adjustments applied to mantissa.
|
||||
int literal_exponent = 0;
|
||||
|
||||
// The type of number scanned.
|
||||
FloatType type = FloatType::kNumber;
|
||||
|
||||
// When non-null, [subrange_begin, subrange_end) marks a range of characters
|
||||
// that require further processing. The meaning is dependent on float type.
|
||||
// If type == kNumber and this is set, this is a "wide input": the input
|
||||
// mantissa contained more than 19 digits. The range contains the full
|
||||
// mantissa. It plus `literal_exponent` need to be examined to find the best
|
||||
// floating point match.
|
||||
// If type == kNan and this is set, the range marks the contents of a
|
||||
// matched parenthesized character region after the NaN.
|
||||
const char* subrange_begin = nullptr;
|
||||
const char* subrange_end = nullptr;
|
||||
|
||||
// One-past-the-end of the successfully parsed region, or nullptr if no
|
||||
// matching pattern was found.
|
||||
const char* end = nullptr;
|
||||
};
|
||||
|
||||
// Read the floating point number in the provided range, and populate
|
||||
// ParsedFloat accordingly.
|
||||
//
|
||||
// format_flags is a bitmask value specifying what patterns this API will match.
|
||||
// `scientific` and `fixed` are honored per std::from_chars rules
|
||||
// ([utility.from.chars], C++17): if exactly one of these bits is set, then an
|
||||
// exponent is required, or dislallowed, respectively.
|
||||
//
|
||||
// Template parameter `base` must be either 10 or 16. For base 16, a "0x" is
|
||||
// *not* consumed. The `hex` bit from format_flags is ignored by ParseFloat.
|
||||
template <int base>
|
||||
ParsedFloat ParseFloat(const char* begin, const char* end,
|
||||
absl::chars_format format_flags);
|
||||
|
||||
extern template ParsedFloat ParseFloat<10>(const char* begin, const char* end,
|
||||
absl::chars_format format_flags);
|
||||
extern template ParsedFloat ParseFloat<16>(const char* begin, const char* end,
|
||||
absl::chars_format format_flags);
|
||||
|
||||
} // namespace strings_internal
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
#endif // ABSL_STRINGS_INTERNAL_CHARCONV_PARSE_H_
|
||||
63
Pods/abseil/absl/strings/internal/cord_data_edge.h
generated
Normal file
63
Pods/abseil/absl/strings/internal/cord_data_edge.h
generated
Normal file
@@ -0,0 +1,63 @@
|
||||
// Copyright 2022 The Abseil Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef ABSL_STRINGS_INTERNAL_CORD_DATA_EDGE_H_
|
||||
#define ABSL_STRINGS_INTERNAL_CORD_DATA_EDGE_H_
|
||||
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
|
||||
#include "absl/base/config.h"
|
||||
#include "absl/strings/internal/cord_internal.h"
|
||||
#include "absl/strings/internal/cord_rep_flat.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace cord_internal {
|
||||
|
||||
// Returns true if the provided rep is a FLAT, EXTERNAL or a SUBSTRING node
|
||||
// holding a FLAT or EXTERNAL child rep. Requires `rep != nullptr`.
|
||||
inline bool IsDataEdge(const CordRep* edge) {
|
||||
assert(edge != nullptr);
|
||||
|
||||
// The fast path is that `edge` is an EXTERNAL or FLAT node, making the below
|
||||
// if a single, well predicted branch. We then repeat the FLAT or EXTERNAL
|
||||
// check in the slow path of the SUBSTRING check to optimize for the hot path.
|
||||
if (edge->tag == EXTERNAL || edge->tag >= FLAT) return true;
|
||||
if (edge->tag == SUBSTRING) edge = edge->substring()->child;
|
||||
return edge->tag == EXTERNAL || edge->tag >= FLAT;
|
||||
}
|
||||
|
||||
// Returns the `absl::string_view` data reference for the provided data edge.
|
||||
// Requires 'IsDataEdge(edge) == true`.
|
||||
inline absl::string_view EdgeData(const CordRep* edge) {
|
||||
assert(IsDataEdge(edge));
|
||||
|
||||
size_t offset = 0;
|
||||
const size_t length = edge->length;
|
||||
if (edge->IsSubstring()) {
|
||||
offset = edge->substring()->start;
|
||||
edge = edge->substring()->child;
|
||||
}
|
||||
return edge->tag >= FLAT
|
||||
? absl::string_view{edge->flat()->Data() + offset, length}
|
||||
: absl::string_view{edge->external()->base + offset, length};
|
||||
}
|
||||
|
||||
} // namespace cord_internal
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
|
||||
#endif // ABSL_STRINGS_INTERNAL_CORD_DATA_EDGE_H_
|
||||
70
Pods/abseil/absl/strings/internal/cord_internal.cc
generated
Normal file
70
Pods/abseil/absl/strings/internal/cord_internal.cc
generated
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.
|
||||
#include "absl/strings/internal/cord_internal.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <memory>
|
||||
|
||||
#include "absl/base/internal/raw_logging.h"
|
||||
#include "absl/container/inlined_vector.h"
|
||||
#include "absl/strings/internal/cord_rep_btree.h"
|
||||
#include "absl/strings/internal/cord_rep_crc.h"
|
||||
#include "absl/strings/internal/cord_rep_flat.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace cord_internal {
|
||||
|
||||
ABSL_CONST_INIT std::atomic<bool> shallow_subcords_enabled(
|
||||
kCordShallowSubcordsDefault);
|
||||
|
||||
void LogFatalNodeType(CordRep* rep) {
|
||||
ABSL_INTERNAL_LOG(FATAL, absl::StrCat("Unexpected node type: ",
|
||||
static_cast<int>(rep->tag)));
|
||||
}
|
||||
|
||||
void CordRep::Destroy(CordRep* rep) {
|
||||
assert(rep != nullptr);
|
||||
|
||||
while (true) {
|
||||
assert(!rep->refcount.IsImmortal());
|
||||
if (rep->tag == BTREE) {
|
||||
CordRepBtree::Destroy(rep->btree());
|
||||
return;
|
||||
} else if (rep->tag == EXTERNAL) {
|
||||
CordRepExternal::Delete(rep);
|
||||
return;
|
||||
} else if (rep->tag == SUBSTRING) {
|
||||
CordRepSubstring* rep_substring = rep->substring();
|
||||
rep = rep_substring->child;
|
||||
delete rep_substring;
|
||||
if (rep->refcount.Decrement()) {
|
||||
return;
|
||||
}
|
||||
} else if (rep->tag == CRC) {
|
||||
CordRepCrc::Destroy(rep->crc());
|
||||
return;
|
||||
} else {
|
||||
assert(rep->IsFlat());
|
||||
CordRepFlat::Delete(rep);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace cord_internal
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
891
Pods/abseil/absl/strings/internal/cord_internal.h
generated
Normal file
891
Pods/abseil/absl/strings/internal/cord_internal.h
generated
Normal file
@@ -0,0 +1,891 @@
|
||||
// Copyright 2021 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_CORD_INTERNAL_H_
|
||||
#define ABSL_STRINGS_INTERNAL_CORD_INTERNAL_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
|
||||
#include "absl/base/attributes.h"
|
||||
#include "absl/base/config.h"
|
||||
#include "absl/base/internal/endian.h"
|
||||
#include "absl/base/internal/invoke.h"
|
||||
#include "absl/base/optimization.h"
|
||||
#include "absl/container/internal/compressed_tuple.h"
|
||||
#include "absl/container/internal/container_memory.h"
|
||||
#include "absl/meta/type_traits.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
|
||||
// We can only add poisoning if we can detect consteval executions.
|
||||
#if defined(ABSL_HAVE_CONSTANT_EVALUATED) && \
|
||||
(defined(ABSL_HAVE_ADDRESS_SANITIZER) || \
|
||||
defined(ABSL_HAVE_MEMORY_SANITIZER))
|
||||
#define ABSL_INTERNAL_CORD_HAVE_SANITIZER 1
|
||||
#endif
|
||||
|
||||
#define ABSL_CORD_INTERNAL_NO_SANITIZE \
|
||||
ABSL_ATTRIBUTE_NO_SANITIZE_ADDRESS ABSL_ATTRIBUTE_NO_SANITIZE_MEMORY
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace cord_internal {
|
||||
|
||||
// The overhead of a vtable is too much for Cord, so we roll our own subclasses
|
||||
// using only a single byte to differentiate classes from each other - the "tag"
|
||||
// byte. Define the subclasses first so we can provide downcasting helper
|
||||
// functions in the base class.
|
||||
struct CordRep;
|
||||
struct CordRepConcat;
|
||||
struct CordRepExternal;
|
||||
struct CordRepFlat;
|
||||
struct CordRepSubstring;
|
||||
struct CordRepCrc;
|
||||
class CordRepBtree;
|
||||
|
||||
class CordzInfo;
|
||||
|
||||
// Default feature enable states for cord ring buffers
|
||||
enum CordFeatureDefaults { kCordShallowSubcordsDefault = false };
|
||||
|
||||
extern std::atomic<bool> shallow_subcords_enabled;
|
||||
|
||||
inline void enable_shallow_subcords(bool enable) {
|
||||
shallow_subcords_enabled.store(enable, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
enum Constants {
|
||||
// The inlined size to use with absl::InlinedVector.
|
||||
//
|
||||
// Note: The InlinedVectors in this file (and in cord.h) do not need to use
|
||||
// the same value for their inlined size. The fact that they do is historical.
|
||||
// It may be desirable for each to use a different inlined size optimized for
|
||||
// that InlinedVector's usage.
|
||||
//
|
||||
// TODO(jgm): Benchmark to see if there's a more optimal value than 47 for
|
||||
// the inlined vector size (47 exists for backward compatibility).
|
||||
kInlinedVectorSize = 47,
|
||||
|
||||
// Prefer copying blocks of at most this size, otherwise reference count.
|
||||
kMaxBytesToCopy = 511
|
||||
};
|
||||
|
||||
// Emits a fatal error "Unexpected node type: xyz" and aborts the program.
|
||||
ABSL_ATTRIBUTE_NORETURN void LogFatalNodeType(CordRep* rep);
|
||||
|
||||
// Fast implementation of memmove for up to 15 bytes. This implementation is
|
||||
// safe for overlapping regions. If nullify_tail is true, the destination is
|
||||
// padded with '\0' up to 15 bytes.
|
||||
template <bool nullify_tail = false>
|
||||
inline void SmallMemmove(char* dst, const char* src, size_t n) {
|
||||
if (n >= 8) {
|
||||
assert(n <= 15);
|
||||
uint64_t buf1;
|
||||
uint64_t buf2;
|
||||
memcpy(&buf1, src, 8);
|
||||
memcpy(&buf2, src + n - 8, 8);
|
||||
if (nullify_tail) {
|
||||
memset(dst + 7, 0, 8);
|
||||
}
|
||||
// GCC 12 has a false-positive -Wstringop-overflow warning here.
|
||||
#if ABSL_INTERNAL_HAVE_MIN_GNUC_VERSION(12, 0)
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wstringop-overflow"
|
||||
#endif
|
||||
memcpy(dst, &buf1, 8);
|
||||
memcpy(dst + n - 8, &buf2, 8);
|
||||
#if ABSL_INTERNAL_HAVE_MIN_GNUC_VERSION(12, 0)
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
} else if (n >= 4) {
|
||||
uint32_t buf1;
|
||||
uint32_t buf2;
|
||||
memcpy(&buf1, src, 4);
|
||||
memcpy(&buf2, src + n - 4, 4);
|
||||
if (nullify_tail) {
|
||||
memset(dst + 4, 0, 4);
|
||||
memset(dst + 7, 0, 8);
|
||||
}
|
||||
memcpy(dst, &buf1, 4);
|
||||
memcpy(dst + n - 4, &buf2, 4);
|
||||
} else {
|
||||
if (n != 0) {
|
||||
dst[0] = src[0];
|
||||
dst[n / 2] = src[n / 2];
|
||||
dst[n - 1] = src[n - 1];
|
||||
}
|
||||
if (nullify_tail) {
|
||||
memset(dst + 7, 0, 8);
|
||||
memset(dst + n, 0, 8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compact class for tracking the reference count and state flags for CordRep
|
||||
// instances. Data is stored in an atomic int32_t for compactness and speed.
|
||||
class RefcountAndFlags {
|
||||
public:
|
||||
constexpr RefcountAndFlags() : count_{kRefIncrement} {}
|
||||
struct Immortal {};
|
||||
explicit constexpr RefcountAndFlags(Immortal) : count_(kImmortalFlag) {}
|
||||
|
||||
// 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.
|
||||
//
|
||||
// Returns false if there are no references outstanding; true otherwise.
|
||||
// Inserts barriers to ensure that state written before this method returns
|
||||
// false will be visible to a thread that just observed this method returning
|
||||
// false. Always returns false when the immortal bit is set.
|
||||
inline bool Decrement() {
|
||||
int32_t refcount = count_.load(std::memory_order_acquire);
|
||||
assert(refcount > 0 || refcount & kImmortalFlag);
|
||||
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(kRefIncrement, std::memory_order_acq_rel);
|
||||
assert(refcount > 0 || refcount & kImmortalFlag);
|
||||
return refcount != kRefIncrement;
|
||||
}
|
||||
|
||||
// Returns the current reference count using acquire semantics.
|
||||
inline size_t Get() const {
|
||||
return static_cast<size_t>(count_.load(std::memory_order_acquire) >>
|
||||
kNumFlags);
|
||||
}
|
||||
|
||||
// Returns whether the atomic integer is 1.
|
||||
// If the reference count is used in the conventional way, a
|
||||
// reference count of 1 implies that the current thread owns the
|
||||
// reference and no other thread shares it.
|
||||
// This call performs the test for a reference count of one, and
|
||||
// performs the memory barrier needed for the owning thread
|
||||
// to act on the object, knowing that it has exclusive access to the
|
||||
// object. Always returns false when the immortal bit is set.
|
||||
inline bool IsOne() {
|
||||
return count_.load(std::memory_order_acquire) == kRefIncrement;
|
||||
}
|
||||
|
||||
bool IsImmortal() const {
|
||||
return (count_.load(std::memory_order_relaxed) & kImmortalFlag) != 0;
|
||||
}
|
||||
|
||||
private:
|
||||
// We reserve the bottom bit for flag.
|
||||
// kImmortalBit indicates that this entity should never be collected; it is
|
||||
// used for the StringConstant constructor to avoid collecting immutable
|
||||
// constant cords.
|
||||
enum Flags {
|
||||
kNumFlags = 1,
|
||||
|
||||
kImmortalFlag = 0x1,
|
||||
kRefIncrement = (1 << kNumFlags),
|
||||
};
|
||||
|
||||
std::atomic<int32_t> count_;
|
||||
};
|
||||
|
||||
// Various representations that we allow
|
||||
enum CordRepKind {
|
||||
UNUSED_0 = 0,
|
||||
SUBSTRING = 1,
|
||||
CRC = 2,
|
||||
BTREE = 3,
|
||||
UNUSED_4 = 4,
|
||||
EXTERNAL = 5,
|
||||
|
||||
// We have different tags for different sized flat arrays,
|
||||
// starting with FLAT, and limited to MAX_FLAT_TAG. The below values map to an
|
||||
// allocated range of 32 bytes to 256 KB. The current granularity is:
|
||||
// - 8 byte granularity for flat sizes in [32 - 512]
|
||||
// - 64 byte granularity for flat sizes in (512 - 8KiB]
|
||||
// - 4KiB byte granularity for flat sizes in (8KiB, 256 KiB]
|
||||
// If a new tag is needed in the future, then 'FLAT' and 'MAX_FLAT_TAG' should
|
||||
// be adjusted as well as the Tag <---> Size mapping logic so that FLAT still
|
||||
// represents the minimum flat allocation size. (32 bytes as of now).
|
||||
FLAT = 6,
|
||||
MAX_FLAT_TAG = 248
|
||||
};
|
||||
|
||||
// There are various locations where we want to check if some rep is a 'plain'
|
||||
// data edge, i.e. an external or flat rep. By having FLAT == EXTERNAL + 1, we
|
||||
// can perform this check in a single branch as 'tag >= EXTERNAL'
|
||||
// Note that we can leave this optimization to the compiler. The compiler will
|
||||
// DTRT when it sees a condition like `tag == EXTERNAL || tag >= FLAT`.
|
||||
static_assert(FLAT == EXTERNAL + 1, "EXTERNAL and FLAT not consecutive");
|
||||
|
||||
struct CordRep {
|
||||
// Result from an `extract edge` operation. Contains the (possibly changed)
|
||||
// tree node as well as the extracted edge, or {tree, nullptr} if no edge
|
||||
// could be extracted.
|
||||
// On success, the returned `tree` value is null if `extracted` was the only
|
||||
// data edge inside the tree, a data edge if there were only two data edges in
|
||||
// the tree, or the (possibly new / smaller) remaining tree with the extracted
|
||||
// data edge removed.
|
||||
struct ExtractResult {
|
||||
CordRep* tree;
|
||||
CordRep* extracted;
|
||||
};
|
||||
|
||||
CordRep() = default;
|
||||
constexpr CordRep(RefcountAndFlags::Immortal immortal, size_t l)
|
||||
: length(l), refcount(immortal), tag(EXTERNAL), storage{} {}
|
||||
|
||||
// The following three fields have to be less than 32 bytes since
|
||||
// that is the smallest supported flat node size. Some code optimizations rely
|
||||
// on the specific layout of these fields. Notably: the non-trivial field
|
||||
// `refcount` being preceded by `length`, and being tailed by POD data
|
||||
// members only.
|
||||
// # LINT.IfChange
|
||||
size_t length;
|
||||
RefcountAndFlags refcount;
|
||||
// If tag < FLAT, it represents CordRepKind and indicates the type of node.
|
||||
// Otherwise, the node type is CordRepFlat and the tag is the encoded size.
|
||||
uint8_t tag;
|
||||
|
||||
// `storage` provides two main purposes:
|
||||
// - the starting point for FlatCordRep.Data() [flexible-array-member]
|
||||
// - 3 bytes of additional storage for use by derived classes.
|
||||
// The latter is used by CordrepConcat and CordRepBtree. CordRepConcat stores
|
||||
// a 'depth' value in storage[0], and the (future) CordRepBtree class stores
|
||||
// `height`, `begin` and `end` in the 3 entries. Otherwise we would need to
|
||||
// allocate room for these in the derived class, as not all compilers reuse
|
||||
// padding space from the base class (clang and gcc do, MSVC does not, etc)
|
||||
uint8_t storage[3];
|
||||
// # LINT.ThenChange(cord_rep_btree.h:copy_raw)
|
||||
|
||||
// Returns true if this instance's tag matches the requested type.
|
||||
constexpr bool IsSubstring() const { return tag == SUBSTRING; }
|
||||
constexpr bool IsCrc() const { return tag == CRC; }
|
||||
constexpr bool IsExternal() const { return tag == EXTERNAL; }
|
||||
constexpr bool IsFlat() const { return tag >= FLAT; }
|
||||
constexpr bool IsBtree() const { return tag == BTREE; }
|
||||
|
||||
inline CordRepSubstring* substring();
|
||||
inline const CordRepSubstring* substring() const;
|
||||
inline CordRepCrc* crc();
|
||||
inline const CordRepCrc* crc() const;
|
||||
inline CordRepExternal* external();
|
||||
inline const CordRepExternal* external() const;
|
||||
inline CordRepFlat* flat();
|
||||
inline const CordRepFlat* flat() const;
|
||||
inline CordRepBtree* btree();
|
||||
inline const CordRepBtree* btree() const;
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
// Memory management
|
||||
|
||||
// Destroys the provided `rep`.
|
||||
static void Destroy(CordRep* rep);
|
||||
|
||||
// Increments the reference count of `rep`.
|
||||
// Requires `rep` to be a non-null pointer value.
|
||||
static inline CordRep* Ref(CordRep* rep);
|
||||
|
||||
// Decrements the reference count of `rep`. Destroys rep if count reaches
|
||||
// zero. Requires `rep` to be a non-null pointer value.
|
||||
static inline void Unref(CordRep* rep);
|
||||
};
|
||||
|
||||
struct CordRepSubstring : public CordRep {
|
||||
size_t start; // Starting offset of substring in child
|
||||
CordRep* child;
|
||||
|
||||
// Creates a substring on `child`, adopting a reference on `child`.
|
||||
// Requires `child` to be either a flat or external node, and `pos` and `n` to
|
||||
// form a non-empty partial sub range of `'child`, i.e.:
|
||||
// `n > 0 && n < length && n + pos <= length`
|
||||
static inline CordRepSubstring* Create(CordRep* child, size_t pos, size_t n);
|
||||
|
||||
// Creates a substring of `rep`. Does not adopt a reference on `rep`.
|
||||
// Requires `IsDataEdge(rep) && n > 0 && pos + n <= rep->length`.
|
||||
// If `n == rep->length` then this method returns `CordRep::Ref(rep)`
|
||||
// If `rep` is a substring of a flat or external node, then this method will
|
||||
// return a new substring of that flat or external node with `pos` adjusted
|
||||
// with the original `start` position.
|
||||
static inline CordRep* Substring(CordRep* rep, size_t pos, size_t n);
|
||||
};
|
||||
|
||||
// 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 CordRepExternal : public CordRep {
|
||||
CordRepExternal() = default;
|
||||
explicit constexpr CordRepExternal(absl::string_view str)
|
||||
: CordRep(RefcountAndFlags::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;
|
||||
|
||||
// Deletes (releases) the external rep.
|
||||
// Requires rep != nullptr and rep->IsExternal()
|
||||
static void Delete(CordRep* rep);
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
inline CordRepSubstring* CordRepSubstring::Create(CordRep* child, size_t pos,
|
||||
size_t n) {
|
||||
assert(child != nullptr);
|
||||
assert(n > 0);
|
||||
assert(n < child->length);
|
||||
assert(pos < child->length);
|
||||
assert(n <= child->length - pos);
|
||||
|
||||
// TODO(b/217376272): Harden internal logic.
|
||||
// Move to strategical places inside the Cord logic and make this an assert.
|
||||
if (ABSL_PREDICT_FALSE(!(child->IsExternal() || child->IsFlat()))) {
|
||||
LogFatalNodeType(child);
|
||||
}
|
||||
|
||||
CordRepSubstring* rep = new CordRepSubstring();
|
||||
rep->length = n;
|
||||
rep->tag = SUBSTRING;
|
||||
rep->start = pos;
|
||||
rep->child = child;
|
||||
return rep;
|
||||
}
|
||||
|
||||
inline CordRep* CordRepSubstring::Substring(CordRep* rep, size_t pos,
|
||||
size_t n) {
|
||||
assert(rep != nullptr);
|
||||
assert(n != 0);
|
||||
assert(pos < rep->length);
|
||||
assert(n <= rep->length - pos);
|
||||
if (n == rep->length) return CordRep::Ref(rep);
|
||||
if (rep->IsSubstring()) {
|
||||
pos += rep->substring()->start;
|
||||
rep = rep->substring()->child;
|
||||
}
|
||||
CordRepSubstring* substr = new CordRepSubstring();
|
||||
substr->length = n;
|
||||
substr->tag = SUBSTRING;
|
||||
substr->start = pos;
|
||||
substr->child = CordRep::Ref(rep);
|
||||
return substr;
|
||||
}
|
||||
|
||||
inline void CordRepExternal::Delete(CordRep* rep) {
|
||||
assert(rep != nullptr && rep->IsExternal());
|
||||
auto* rep_external = static_cast<CordRepExternal*>(rep);
|
||||
assert(rep_external->releaser_invoker != nullptr);
|
||||
rep_external->releaser_invoker(rep_external);
|
||||
}
|
||||
|
||||
template <typename Str>
|
||||
struct ConstInitExternalStorage {
|
||||
ABSL_CONST_INIT static CordRepExternal value;
|
||||
};
|
||||
|
||||
template <typename Str>
|
||||
ABSL_CONST_INIT CordRepExternal
|
||||
ConstInitExternalStorage<Str>::value(Str::value);
|
||||
|
||||
enum {
|
||||
kMaxInline = 15,
|
||||
};
|
||||
|
||||
constexpr char GetOrNull(absl::string_view data, size_t pos) {
|
||||
return pos < data.size() ? data[pos] : '\0';
|
||||
}
|
||||
|
||||
// We store cordz_info as 64 bit pointer value in little endian format. This
|
||||
// guarantees that the least significant byte of cordz_info matches the first
|
||||
// byte of the inline data representation in `data`, which holds the inlined
|
||||
// size or the 'is_tree' bit.
|
||||
using cordz_info_t = int64_t;
|
||||
|
||||
// Assert that the `cordz_info` pointer value perfectly overlaps the last half
|
||||
// of `data` and can hold a pointer value.
|
||||
static_assert(sizeof(cordz_info_t) * 2 == kMaxInline + 1, "");
|
||||
static_assert(sizeof(cordz_info_t) >= sizeof(intptr_t), "");
|
||||
|
||||
// LittleEndianByte() creates a little endian representation of 'value', i.e.:
|
||||
// a little endian value where the first byte in the host's representation
|
||||
// holds 'value`, with all other bytes being 0.
|
||||
static constexpr cordz_info_t LittleEndianByte(unsigned char value) {
|
||||
#if defined(ABSL_IS_BIG_ENDIAN)
|
||||
return static_cast<cordz_info_t>(value) << ((sizeof(cordz_info_t) - 1) * 8);
|
||||
#else
|
||||
return value;
|
||||
#endif
|
||||
}
|
||||
|
||||
class InlineData {
|
||||
public:
|
||||
// DefaultInitType forces the use of the default initialization constructor.
|
||||
enum DefaultInitType { kDefaultInit };
|
||||
|
||||
// kNullCordzInfo holds the little endian representation of intptr_t(1)
|
||||
// This is the 'null' / initial value of 'cordz_info'. The null value
|
||||
// is specifically big endian 1 as with 64-bit pointers, the last
|
||||
// byte of cordz_info overlaps with the last byte holding the tag.
|
||||
static constexpr cordz_info_t kNullCordzInfo = LittleEndianByte(1);
|
||||
|
||||
// kTagOffset contains the offset of the control byte / tag. This constant is
|
||||
// intended mostly for debugging purposes: do not remove this constant as it
|
||||
// is actively inspected and used by gdb pretty printing code.
|
||||
static constexpr size_t kTagOffset = 0;
|
||||
|
||||
// Implement `~InlineData()` conditionally: we only need this destructor to
|
||||
// unpoison poisoned instances under *SAN, and it will only compile correctly
|
||||
// if the current compiler supports `absl::is_constant_evaluated()`.
|
||||
#ifdef ABSL_INTERNAL_CORD_HAVE_SANITIZER
|
||||
~InlineData() noexcept { unpoison(); }
|
||||
#endif
|
||||
|
||||
constexpr InlineData() noexcept { poison_this(); }
|
||||
|
||||
explicit InlineData(DefaultInitType) noexcept : rep_(kDefaultInit) {
|
||||
poison_this();
|
||||
}
|
||||
|
||||
explicit InlineData(CordRep* rep) noexcept : rep_(rep) {
|
||||
ABSL_ASSERT(rep != nullptr);
|
||||
}
|
||||
|
||||
// Explicit constexpr constructor to create a constexpr InlineData
|
||||
// value. Creates an inlined SSO value if `rep` is null, otherwise
|
||||
// creates a tree instance value.
|
||||
constexpr InlineData(absl::string_view sv, CordRep* rep) noexcept
|
||||
: rep_(rep ? Rep(rep) : Rep(sv)) {
|
||||
poison();
|
||||
}
|
||||
|
||||
constexpr InlineData(const InlineData& rhs) noexcept;
|
||||
InlineData& operator=(const InlineData& rhs) noexcept;
|
||||
|
||||
friend bool operator==(const InlineData& lhs, const InlineData& rhs) {
|
||||
#ifdef ABSL_INTERNAL_CORD_HAVE_SANITIZER
|
||||
const Rep l = lhs.rep_.SanitizerSafeCopy();
|
||||
const Rep r = rhs.rep_.SanitizerSafeCopy();
|
||||
return memcmp(&l, &r, sizeof(l)) == 0;
|
||||
#else
|
||||
return memcmp(&lhs, &rhs, sizeof(lhs)) == 0;
|
||||
#endif
|
||||
}
|
||||
friend bool operator!=(const InlineData& lhs, const InlineData& rhs) {
|
||||
return !operator==(lhs, rhs);
|
||||
}
|
||||
|
||||
// Poisons the unused inlined SSO data if the current instance
|
||||
// is inlined, else un-poisons the entire instance.
|
||||
constexpr void poison();
|
||||
|
||||
// Un-poisons this instance.
|
||||
constexpr void unpoison();
|
||||
|
||||
// Poisons the current instance. This is used on default initialization.
|
||||
constexpr void poison_this();
|
||||
|
||||
// Returns true if the current instance is empty.
|
||||
// The 'empty value' is an inlined data value of zero length.
|
||||
bool is_empty() const { return rep_.tag() == 0; }
|
||||
|
||||
// Returns true if the current instance holds a tree value.
|
||||
bool is_tree() const { return (rep_.tag() & 1) != 0; }
|
||||
|
||||
// Returns true if the current instance holds a cordz_info value.
|
||||
// Requires the current instance to hold a tree value.
|
||||
bool is_profiled() const {
|
||||
assert(is_tree());
|
||||
return rep_.cordz_info() != kNullCordzInfo;
|
||||
}
|
||||
|
||||
// Returns true if either of the provided instances hold a cordz_info value.
|
||||
// This method is more efficient than the equivalent `data1.is_profiled() ||
|
||||
// data2.is_profiled()`. Requires both arguments to hold a tree.
|
||||
static bool is_either_profiled(const InlineData& data1,
|
||||
const InlineData& data2) {
|
||||
assert(data1.is_tree() && data2.is_tree());
|
||||
return (data1.rep_.cordz_info() | data2.rep_.cordz_info()) !=
|
||||
kNullCordzInfo;
|
||||
}
|
||||
|
||||
// Returns the cordz_info sampling instance for this instance, or nullptr
|
||||
// if the current instance is not sampled and does not have CordzInfo data.
|
||||
// Requires the current instance to hold a tree value.
|
||||
CordzInfo* cordz_info() const {
|
||||
assert(is_tree());
|
||||
intptr_t info = static_cast<intptr_t>(absl::little_endian::ToHost64(
|
||||
static_cast<uint64_t>(rep_.cordz_info())));
|
||||
assert(info & 1);
|
||||
return reinterpret_cast<CordzInfo*>(info - 1);
|
||||
}
|
||||
|
||||
// Sets the current cordz_info sampling instance for this instance, or nullptr
|
||||
// if the current instance is not sampled and does not have CordzInfo data.
|
||||
// Requires the current instance to hold a tree value.
|
||||
void set_cordz_info(CordzInfo* cordz_info) {
|
||||
assert(is_tree());
|
||||
uintptr_t info = reinterpret_cast<uintptr_t>(cordz_info) | 1;
|
||||
rep_.set_cordz_info(
|
||||
static_cast<cordz_info_t>(absl::little_endian::FromHost64(info)));
|
||||
}
|
||||
|
||||
// Resets the current cordz_info to null / empty.
|
||||
void clear_cordz_info() {
|
||||
assert(is_tree());
|
||||
rep_.set_cordz_info(kNullCordzInfo);
|
||||
}
|
||||
|
||||
// Returns a read only pointer to the character data inside this instance.
|
||||
// Requires the current instance to hold inline data.
|
||||
const char* as_chars() const {
|
||||
assert(!is_tree());
|
||||
return rep_.as_chars();
|
||||
}
|
||||
|
||||
// Returns a mutable pointer to the character data inside this instance.
|
||||
// Should be used for 'write only' operations setting an inlined value.
|
||||
// Applications can set the value of inlined data either before or after
|
||||
// setting the inlined size, i.e., both of the below are valid:
|
||||
//
|
||||
// // Set inlined data and inline size
|
||||
// memcpy(data_.as_chars(), data, size);
|
||||
// data_.set_inline_size(size);
|
||||
//
|
||||
// // Set inlined size and inline data
|
||||
// data_.set_inline_size(size);
|
||||
// memcpy(data_.as_chars(), data, size);
|
||||
//
|
||||
// It's an error to read from the returned pointer without a preceding write
|
||||
// if the current instance does not hold inline data, i.e.: is_tree() == true.
|
||||
char* as_chars() { return rep_.as_chars(); }
|
||||
|
||||
// Returns the tree value of this value.
|
||||
// Requires the current instance to hold a tree value.
|
||||
CordRep* as_tree() const {
|
||||
assert(is_tree());
|
||||
return rep_.tree();
|
||||
}
|
||||
|
||||
void set_inline_data(const char* data, size_t n) {
|
||||
ABSL_ASSERT(n <= kMaxInline);
|
||||
unpoison();
|
||||
rep_.set_tag(static_cast<int8_t>(n << 1));
|
||||
SmallMemmove<true>(rep_.as_chars(), data, n);
|
||||
poison();
|
||||
}
|
||||
|
||||
void copy_max_inline_to(char* dst) const {
|
||||
assert(!is_tree());
|
||||
memcpy(dst, rep_.SanitizerSafeCopy().as_chars(), kMaxInline);
|
||||
}
|
||||
|
||||
// Initialize this instance to holding the tree value `rep`,
|
||||
// initializing the cordz_info to null, i.e.: 'not profiled'.
|
||||
void make_tree(CordRep* rep) {
|
||||
unpoison();
|
||||
rep_.make_tree(rep);
|
||||
}
|
||||
|
||||
// Set the tree value of this instance to 'rep`.
|
||||
// Requires the current instance to already hold a tree value.
|
||||
// Does not affect the value of cordz_info.
|
||||
void set_tree(CordRep* rep) {
|
||||
assert(is_tree());
|
||||
rep_.set_tree(rep);
|
||||
}
|
||||
|
||||
// Returns the size of the inlined character data inside this instance.
|
||||
// Requires the current instance to hold inline data.
|
||||
size_t inline_size() const { return rep_.inline_size(); }
|
||||
|
||||
// Sets the size of the inlined character data inside this instance.
|
||||
// Requires `size` to be <= kMaxInline.
|
||||
// See the documentation on 'as_chars()' for more information and examples.
|
||||
void set_inline_size(size_t size) {
|
||||
unpoison();
|
||||
rep_.set_inline_size(size);
|
||||
poison();
|
||||
}
|
||||
|
||||
// Compares 'this' inlined data with rhs. The comparison is a straightforward
|
||||
// lexicographic comparison. `Compare()` returns values as follows:
|
||||
//
|
||||
// -1 'this' InlineData instance is smaller
|
||||
// 0 the InlineData instances are equal
|
||||
// 1 'this' InlineData instance larger
|
||||
int Compare(const InlineData& rhs) const {
|
||||
return Compare(rep_.SanitizerSafeCopy(), rhs.rep_.SanitizerSafeCopy());
|
||||
}
|
||||
|
||||
private:
|
||||
struct Rep {
|
||||
// See cordz_info_t for forced alignment and size of `cordz_info` details.
|
||||
struct AsTree {
|
||||
explicit constexpr AsTree(absl::cord_internal::CordRep* tree)
|
||||
: rep(tree) {}
|
||||
cordz_info_t cordz_info = kNullCordzInfo;
|
||||
absl::cord_internal::CordRep* rep;
|
||||
};
|
||||
|
||||
explicit Rep(DefaultInitType) {}
|
||||
constexpr Rep() : data{0} {}
|
||||
constexpr Rep(const Rep&) = default;
|
||||
constexpr Rep& operator=(const Rep&) = default;
|
||||
|
||||
explicit constexpr Rep(CordRep* rep) : as_tree(rep) {}
|
||||
|
||||
explicit constexpr Rep(absl::string_view chars)
|
||||
: data{static_cast<char>((chars.size() << 1)),
|
||||
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)} {}
|
||||
|
||||
// Disable sanitizer as we must always be able to read `tag`.
|
||||
ABSL_CORD_INTERNAL_NO_SANITIZE
|
||||
int8_t tag() const { return reinterpret_cast<const int8_t*>(this)[0]; }
|
||||
void set_tag(int8_t rhs) { reinterpret_cast<int8_t*>(this)[0] = rhs; }
|
||||
|
||||
char* as_chars() { return data + 1; }
|
||||
const char* as_chars() const { return data + 1; }
|
||||
|
||||
bool is_tree() const { return (tag() & 1) != 0; }
|
||||
|
||||
size_t inline_size() const {
|
||||
ABSL_ASSERT(!is_tree());
|
||||
return static_cast<size_t>(tag()) >> 1;
|
||||
}
|
||||
|
||||
void set_inline_size(size_t size) {
|
||||
ABSL_ASSERT(size <= kMaxInline);
|
||||
set_tag(static_cast<int8_t>(size << 1));
|
||||
}
|
||||
|
||||
CordRep* tree() const { return as_tree.rep; }
|
||||
void set_tree(CordRep* rhs) { as_tree.rep = rhs; }
|
||||
|
||||
cordz_info_t cordz_info() const { return as_tree.cordz_info; }
|
||||
void set_cordz_info(cordz_info_t rhs) { as_tree.cordz_info = rhs; }
|
||||
|
||||
void make_tree(CordRep* tree) {
|
||||
as_tree.rep = tree;
|
||||
as_tree.cordz_info = kNullCordzInfo;
|
||||
}
|
||||
|
||||
#ifdef ABSL_INTERNAL_CORD_HAVE_SANITIZER
|
||||
constexpr Rep SanitizerSafeCopy() const {
|
||||
if (!absl::is_constant_evaluated()) {
|
||||
Rep res;
|
||||
if (is_tree()) {
|
||||
res = *this;
|
||||
} else {
|
||||
res.set_tag(tag());
|
||||
memcpy(res.as_chars(), as_chars(), inline_size());
|
||||
}
|
||||
return res;
|
||||
} else {
|
||||
return *this;
|
||||
}
|
||||
}
|
||||
#else
|
||||
constexpr const Rep& SanitizerSafeCopy() const { return *this; }
|
||||
#endif
|
||||
|
||||
// If the data has length <= kMaxInline, we store it in `data`, and
|
||||
// store the size in the first char of `data` shifted left + 1.
|
||||
// Else we store it in a tree and store a pointer to that tree in
|
||||
// `as_tree.rep` with a tagged pointer to make `tag() & 1` non zero.
|
||||
union {
|
||||
char data[kMaxInline + 1];
|
||||
AsTree as_tree;
|
||||
};
|
||||
};
|
||||
|
||||
// Private implementation of `Compare()`
|
||||
static inline int Compare(const Rep& lhs, const Rep& rhs) {
|
||||
uint64_t x, y;
|
||||
memcpy(&x, lhs.as_chars(), sizeof(x));
|
||||
memcpy(&y, rhs.as_chars(), sizeof(y));
|
||||
if (x == y) {
|
||||
memcpy(&x, lhs.as_chars() + 7, sizeof(x));
|
||||
memcpy(&y, rhs.as_chars() + 7, sizeof(y));
|
||||
if (x == y) {
|
||||
if (lhs.inline_size() == rhs.inline_size()) return 0;
|
||||
return lhs.inline_size() < rhs.inline_size() ? -1 : 1;
|
||||
}
|
||||
}
|
||||
x = absl::big_endian::FromHost64(x);
|
||||
y = absl::big_endian::FromHost64(y);
|
||||
return x < y ? -1 : 1;
|
||||
}
|
||||
|
||||
Rep rep_;
|
||||
};
|
||||
|
||||
static_assert(sizeof(InlineData) == kMaxInline + 1, "");
|
||||
|
||||
#ifdef ABSL_INTERNAL_CORD_HAVE_SANITIZER
|
||||
|
||||
constexpr InlineData::InlineData(const InlineData& rhs) noexcept
|
||||
: rep_(rhs.rep_.SanitizerSafeCopy()) {
|
||||
poison();
|
||||
}
|
||||
|
||||
inline InlineData& InlineData::operator=(const InlineData& rhs) noexcept {
|
||||
unpoison();
|
||||
rep_ = rhs.rep_.SanitizerSafeCopy();
|
||||
poison();
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr void InlineData::poison_this() {
|
||||
if (!absl::is_constant_evaluated()) {
|
||||
container_internal::SanitizerPoisonObject(this);
|
||||
}
|
||||
}
|
||||
|
||||
constexpr void InlineData::unpoison() {
|
||||
if (!absl::is_constant_evaluated()) {
|
||||
container_internal::SanitizerUnpoisonObject(this);
|
||||
}
|
||||
}
|
||||
|
||||
constexpr void InlineData::poison() {
|
||||
if (!absl::is_constant_evaluated()) {
|
||||
if (is_tree()) {
|
||||
container_internal::SanitizerUnpoisonObject(this);
|
||||
} else if (const size_t size = inline_size()) {
|
||||
if (size < kMaxInline) {
|
||||
const char* end = rep_.as_chars() + size;
|
||||
container_internal::SanitizerPoisonMemoryRegion(end, kMaxInline - size);
|
||||
}
|
||||
} else {
|
||||
container_internal::SanitizerPoisonObject(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#else // ABSL_INTERNAL_CORD_HAVE_SANITIZER
|
||||
|
||||
constexpr InlineData::InlineData(const InlineData&) noexcept = default;
|
||||
inline InlineData& InlineData::operator=(const InlineData&) noexcept = default;
|
||||
|
||||
constexpr void InlineData::poison_this() {}
|
||||
constexpr void InlineData::unpoison() {}
|
||||
constexpr void InlineData::poison() {}
|
||||
|
||||
#endif // ABSL_INTERNAL_CORD_HAVE_SANITIZER
|
||||
|
||||
inline CordRepSubstring* CordRep::substring() {
|
||||
assert(IsSubstring());
|
||||
return static_cast<CordRepSubstring*>(this);
|
||||
}
|
||||
|
||||
inline const CordRepSubstring* CordRep::substring() const {
|
||||
assert(IsSubstring());
|
||||
return static_cast<const CordRepSubstring*>(this);
|
||||
}
|
||||
|
||||
inline CordRepExternal* CordRep::external() {
|
||||
assert(IsExternal());
|
||||
return static_cast<CordRepExternal*>(this);
|
||||
}
|
||||
|
||||
inline const CordRepExternal* CordRep::external() const {
|
||||
assert(IsExternal());
|
||||
return static_cast<const CordRepExternal*>(this);
|
||||
}
|
||||
|
||||
inline CordRep* CordRep::Ref(CordRep* rep) {
|
||||
// ABSL_ASSUME is a workaround for
|
||||
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105585
|
||||
ABSL_ASSUME(rep != nullptr);
|
||||
rep->refcount.Increment();
|
||||
return rep;
|
||||
}
|
||||
|
||||
inline void CordRep::Unref(CordRep* rep) {
|
||||
assert(rep != nullptr);
|
||||
// Expect refcount to be 0. Avoiding the cost of an atomic decrement should
|
||||
// typically outweigh the cost of an extra branch checking for ref == 1.
|
||||
if (ABSL_PREDICT_FALSE(!rep->refcount.DecrementExpectHighRefcount())) {
|
||||
Destroy(rep);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace cord_internal
|
||||
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
#endif // ABSL_STRINGS_INTERNAL_CORD_INTERNAL_H_
|
||||
1241
Pods/abseil/absl/strings/internal/cord_rep_btree.cc
generated
Normal file
1241
Pods/abseil/absl/strings/internal/cord_rep_btree.cc
generated
Normal file
@@ -0,0 +1,1241 @@
|
||||
// Copyright 2021 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/cord_rep_btree.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
#include "absl/base/attributes.h"
|
||||
#include "absl/base/config.h"
|
||||
#include "absl/base/internal/raw_logging.h"
|
||||
#include "absl/base/optimization.h"
|
||||
#include "absl/strings/internal/cord_data_edge.h"
|
||||
#include "absl/strings/internal/cord_internal.h"
|
||||
#include "absl/strings/internal/cord_rep_consume.h"
|
||||
#include "absl/strings/internal/cord_rep_flat.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace cord_internal {
|
||||
|
||||
#ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL
|
||||
constexpr size_t CordRepBtree::kMaxCapacity;
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
using NodeStack = CordRepBtree * [CordRepBtree::kMaxDepth];
|
||||
using EdgeType = CordRepBtree::EdgeType;
|
||||
using OpResult = CordRepBtree::OpResult;
|
||||
using CopyResult = CordRepBtree::CopyResult;
|
||||
|
||||
constexpr auto kFront = CordRepBtree::kFront;
|
||||
constexpr auto kBack = CordRepBtree::kBack;
|
||||
|
||||
ABSL_CONST_INIT std::atomic<bool> cord_btree_exhaustive_validation(false);
|
||||
|
||||
// Implementation of the various 'Dump' functions.
|
||||
// Prints the entire tree structure or 'rep'. External callers should
|
||||
// not specify 'depth' and leave it to its default (0) value.
|
||||
// Rep may be a CordRepBtree tree, or a SUBSTRING / EXTERNAL / FLAT node.
|
||||
void DumpAll(const CordRep* rep,
|
||||
bool include_contents,
|
||||
std::ostream& stream,
|
||||
size_t depth = 0) {
|
||||
// Allow for full height trees + substring -> flat / external nodes.
|
||||
assert(depth <= CordRepBtree::kMaxDepth + 2);
|
||||
std::string sharing = const_cast<CordRep*>(rep)->refcount.IsOne()
|
||||
? std::string("Private")
|
||||
: absl::StrCat("Shared(", rep->refcount.Get(), ")");
|
||||
std::string sptr = absl::StrCat("0x", absl::Hex(rep));
|
||||
|
||||
// Dumps the data contents of `rep` if `include_contents` is true.
|
||||
// Always emits a new line character.
|
||||
auto maybe_dump_data = [&stream, include_contents](const CordRep* r) {
|
||||
if (include_contents) {
|
||||
// Allow for up to 60 wide display of content data, which with some
|
||||
// indentation and prefix / labels keeps us within roughly 80-100 wide.
|
||||
constexpr size_t kMaxDataLength = 60;
|
||||
stream << ", data = \""
|
||||
<< EdgeData(r).substr(0, kMaxDataLength)
|
||||
<< (r->length > kMaxDataLength ? "\"..." : "\"");
|
||||
}
|
||||
stream << '\n';
|
||||
};
|
||||
|
||||
// For each level, we print the 'shared/private' state and the rep pointer,
|
||||
// indented by two spaces per recursive depth.
|
||||
stream << std::string(depth * 2, ' ') << sharing << " (" << sptr << ") ";
|
||||
|
||||
if (rep->IsBtree()) {
|
||||
const CordRepBtree* node = rep->btree();
|
||||
std::string label =
|
||||
node->height() ? absl::StrCat("Node(", node->height(), ")") : "Leaf";
|
||||
stream << label << ", len = " << node->length
|
||||
<< ", begin = " << node->begin() << ", end = " << node->end()
|
||||
<< "\n";
|
||||
for (CordRep* edge : node->Edges()) {
|
||||
DumpAll(edge, include_contents, stream, depth + 1);
|
||||
}
|
||||
} else if (rep->tag == SUBSTRING) {
|
||||
const CordRepSubstring* substring = rep->substring();
|
||||
stream << "Substring, len = " << rep->length
|
||||
<< ", start = " << substring->start;
|
||||
maybe_dump_data(rep);
|
||||
DumpAll(substring->child, include_contents, stream, depth + 1);
|
||||
} else if (rep->tag >= FLAT) {
|
||||
stream << "Flat, len = " << rep->length
|
||||
<< ", cap = " << rep->flat()->Capacity();
|
||||
maybe_dump_data(rep);
|
||||
} else if (rep->tag == EXTERNAL) {
|
||||
stream << "Extn, len = " << rep->length;
|
||||
maybe_dump_data(rep);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(b/192061034): add 'bytes to copy' logic to avoid large slop on substring
|
||||
// small data out of large reps, and general efficiency of 'always copy small
|
||||
// data'. Consider making this a cord rep internal library function.
|
||||
CordRepSubstring* CreateSubstring(CordRep* rep, size_t offset, size_t n) {
|
||||
assert(n != 0);
|
||||
assert(offset + n <= rep->length);
|
||||
assert(offset != 0 || n != rep->length);
|
||||
|
||||
if (rep->tag == SUBSTRING) {
|
||||
CordRepSubstring* substring = rep->substring();
|
||||
offset += substring->start;
|
||||
rep = CordRep::Ref(substring->child);
|
||||
CordRep::Unref(substring);
|
||||
}
|
||||
assert(rep->IsExternal() || rep->IsFlat());
|
||||
CordRepSubstring* substring = new CordRepSubstring();
|
||||
substring->length = n;
|
||||
substring->tag = SUBSTRING;
|
||||
substring->start = offset;
|
||||
substring->child = rep;
|
||||
return substring;
|
||||
}
|
||||
|
||||
// TODO(b/192061034): consider making this a cord rep library function.
|
||||
inline CordRep* MakeSubstring(CordRep* rep, size_t offset, size_t n) {
|
||||
if (n == rep->length) return rep;
|
||||
if (n == 0) return CordRep::Unref(rep), nullptr;
|
||||
return CreateSubstring(rep, offset, n);
|
||||
}
|
||||
|
||||
// TODO(b/192061034): consider making this a cord rep library function.
|
||||
inline CordRep* MakeSubstring(CordRep* rep, size_t offset) {
|
||||
if (offset == 0) return rep;
|
||||
return CreateSubstring(rep, offset, rep->length - offset);
|
||||
}
|
||||
|
||||
// Resizes `edge` to the provided `length`. Adopts a reference on `edge`.
|
||||
// This method directly returns `edge` if `length` equals `edge->length`.
|
||||
// If `is_mutable` is set to true, this function may return `edge` with
|
||||
// `edge->length` set to the new length depending on the type and size of
|
||||
// `edge`. Otherwise, this function returns a new CordRepSubstring value.
|
||||
// Requires `length > 0 && length <= edge->length`.
|
||||
CordRep* ResizeEdge(CordRep* edge, size_t length, bool is_mutable) {
|
||||
assert(length > 0);
|
||||
assert(length <= edge->length);
|
||||
assert(IsDataEdge(edge));
|
||||
if (length >= edge->length) return edge;
|
||||
|
||||
if (is_mutable && (edge->tag >= FLAT || edge->tag == SUBSTRING)) {
|
||||
edge->length = length;
|
||||
return edge;
|
||||
}
|
||||
|
||||
return CreateSubstring(edge, 0, length);
|
||||
}
|
||||
|
||||
template <EdgeType edge_type>
|
||||
inline absl::string_view Consume(absl::string_view s, size_t n) {
|
||||
return edge_type == kBack ? s.substr(n) : s.substr(0, s.size() - n);
|
||||
}
|
||||
|
||||
template <EdgeType edge_type>
|
||||
inline absl::string_view Consume(char* dst, absl::string_view s, size_t n) {
|
||||
if (edge_type == kBack) {
|
||||
memcpy(dst, s.data(), n);
|
||||
return s.substr(n);
|
||||
} else {
|
||||
const size_t offset = s.size() - n;
|
||||
memcpy(dst, s.data() + offset, n);
|
||||
return s.substr(0, offset);
|
||||
}
|
||||
}
|
||||
|
||||
// Known issue / optimization weirdness: the store associated with the
|
||||
// decrement introduces traffic between cpus (even if the result of that
|
||||
// traffic does nothing), making this faster than a single call to
|
||||
// refcount.Decrement() checking the zero refcount condition.
|
||||
template <typename R, typename Fn>
|
||||
inline void FastUnref(R* r, Fn&& fn) {
|
||||
if (r->refcount.IsOne()) {
|
||||
fn(r);
|
||||
} else if (!r->refcount.DecrementExpectHighRefcount()) {
|
||||
fn(r);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void DeleteSubstring(CordRepSubstring* substring) {
|
||||
CordRep* rep = substring->child;
|
||||
if (!rep->refcount.Decrement()) {
|
||||
if (rep->tag >= FLAT) {
|
||||
CordRepFlat::Delete(rep->flat());
|
||||
} else {
|
||||
assert(rep->tag == EXTERNAL);
|
||||
CordRepExternal::Delete(rep->external());
|
||||
}
|
||||
}
|
||||
delete substring;
|
||||
}
|
||||
|
||||
// Deletes a leaf node data edge. Requires `IsDataEdge(rep)`.
|
||||
void DeleteLeafEdge(CordRep* rep) {
|
||||
assert(IsDataEdge(rep));
|
||||
if (rep->tag >= FLAT) {
|
||||
CordRepFlat::Delete(rep->flat());
|
||||
} else if (rep->tag == EXTERNAL) {
|
||||
CordRepExternal::Delete(rep->external());
|
||||
} else {
|
||||
DeleteSubstring(rep->substring());
|
||||
}
|
||||
}
|
||||
|
||||
// StackOperations contains the logic to build a left-most or right-most stack
|
||||
// (leg) down to the leaf level of a btree, and 'unwind' / 'Finalize' methods to
|
||||
// propagate node changes up the stack.
|
||||
template <EdgeType edge_type>
|
||||
struct StackOperations {
|
||||
// Returns true if the node at 'depth' is not shared, i.e. has a refcount
|
||||
// of one and all of its parent nodes have a refcount of one.
|
||||
inline bool owned(int depth) const { return depth < share_depth; }
|
||||
|
||||
// Returns the node at 'depth'.
|
||||
inline CordRepBtree* node(int depth) const { return stack[depth]; }
|
||||
|
||||
// Builds a `depth` levels deep stack starting at `tree` recording which nodes
|
||||
// are private in the form of the 'share depth' where nodes are shared.
|
||||
inline CordRepBtree* BuildStack(CordRepBtree* tree, int depth) {
|
||||
assert(depth <= tree->height());
|
||||
int current_depth = 0;
|
||||
while (current_depth < depth && tree->refcount.IsOne()) {
|
||||
stack[current_depth++] = tree;
|
||||
tree = tree->Edge(edge_type)->btree();
|
||||
}
|
||||
share_depth = current_depth + (tree->refcount.IsOne() ? 1 : 0);
|
||||
while (current_depth < depth) {
|
||||
stack[current_depth++] = tree;
|
||||
tree = tree->Edge(edge_type)->btree();
|
||||
}
|
||||
return tree;
|
||||
}
|
||||
|
||||
// Builds a stack with the invariant that all nodes are private owned / not
|
||||
// shared. This is used in iterative updates where a previous propagation
|
||||
// guaranteed all nodes are owned / private.
|
||||
inline void BuildOwnedStack(CordRepBtree* tree, int height) {
|
||||
assert(height <= CordRepBtree::kMaxHeight);
|
||||
int depth = 0;
|
||||
while (depth < height) {
|
||||
assert(tree->refcount.IsOne());
|
||||
stack[depth++] = tree;
|
||||
tree = tree->Edge(edge_type)->btree();
|
||||
}
|
||||
assert(tree->refcount.IsOne());
|
||||
share_depth = depth + 1;
|
||||
}
|
||||
|
||||
// Processes the final 'top level' result action for the tree.
|
||||
// See the 'Action' enum for the various action implications.
|
||||
static inline CordRepBtree* Finalize(CordRepBtree* tree, OpResult result) {
|
||||
switch (result.action) {
|
||||
case CordRepBtree::kPopped:
|
||||
tree = edge_type == kBack ? CordRepBtree::New(tree, result.tree)
|
||||
: CordRepBtree::New(result.tree, tree);
|
||||
if (ABSL_PREDICT_FALSE(tree->height() > CordRepBtree::kMaxHeight)) {
|
||||
tree = CordRepBtree::Rebuild(tree);
|
||||
ABSL_RAW_CHECK(tree->height() <= CordRepBtree::kMaxHeight,
|
||||
"Max height exceeded");
|
||||
}
|
||||
return tree;
|
||||
case CordRepBtree::kCopied:
|
||||
CordRep::Unref(tree);
|
||||
ABSL_FALLTHROUGH_INTENDED;
|
||||
case CordRepBtree::kSelf:
|
||||
return result.tree;
|
||||
}
|
||||
ABSL_UNREACHABLE();
|
||||
return result.tree;
|
||||
}
|
||||
|
||||
// Propagate the action result in 'result' up into all nodes of the stack
|
||||
// starting at depth 'depth'. 'length' contains the extra length of data that
|
||||
// was added at the lowest level, and is updated into all nodes of the stack.
|
||||
// See the 'Action' enum for the various action implications.
|
||||
// If 'propagate' is true, then any copied node values are updated into the
|
||||
// stack, which is used for iterative processing on the same stack.
|
||||
template <bool propagate = false>
|
||||
inline CordRepBtree* Unwind(CordRepBtree* tree, int depth, size_t length,
|
||||
OpResult result) {
|
||||
// TODO(mvels): revisit the below code to check if 3 loops with 3
|
||||
// (incremental) conditions is faster than 1 loop with a switch.
|
||||
// Benchmarking and perf recordings indicate the loop with switch is
|
||||
// fastest, likely because of indirect jumps on the tight case values and
|
||||
// dense branches. But it's worth considering 3 loops, as the `action`
|
||||
// transitions are mono directional. E.g.:
|
||||
// while (action == kPopped) {
|
||||
// ...
|
||||
// }
|
||||
// while (action == kCopied) {
|
||||
// ...
|
||||
// }
|
||||
// ...
|
||||
// We also found that an "if () do {}" loop here seems faster, possibly
|
||||
// because it allows the branch predictor more granular heuristics on
|
||||
// 'single leaf' (`depth` == 0) and 'single depth' (`depth` == 1) cases
|
||||
// which appear to be the most common use cases.
|
||||
if (depth != 0) {
|
||||
do {
|
||||
CordRepBtree* node = stack[--depth];
|
||||
const bool owned = depth < share_depth;
|
||||
switch (result.action) {
|
||||
case CordRepBtree::kPopped:
|
||||
assert(!propagate);
|
||||
result = node->AddEdge<edge_type>(owned, result.tree, length);
|
||||
break;
|
||||
case CordRepBtree::kCopied:
|
||||
result = node->SetEdge<edge_type>(owned, result.tree, length);
|
||||
if (propagate) stack[depth] = result.tree;
|
||||
break;
|
||||
case CordRepBtree::kSelf:
|
||||
node->length += length;
|
||||
while (depth > 0) {
|
||||
node = stack[--depth];
|
||||
node->length += length;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
} while (depth > 0);
|
||||
}
|
||||
return Finalize(tree, result);
|
||||
}
|
||||
|
||||
// Invokes `Unwind` with `propagate=true` to update the stack node values.
|
||||
inline CordRepBtree* Propagate(CordRepBtree* tree, int depth, size_t length,
|
||||
OpResult result) {
|
||||
return Unwind</*propagate=*/true>(tree, depth, length, result);
|
||||
}
|
||||
|
||||
// `share_depth` contains the depth at which the nodes in the stack become
|
||||
// shared. I.e., if the top most level is shared (i.e.: `!refcount.IsOne()`),
|
||||
// then `share_depth` is 0. If the 2nd node is shared (and implicitly all
|
||||
// nodes below that) then `share_depth` is 1, etc. A `share_depth` greater
|
||||
// than the depth of the stack indicates that none of the nodes in the stack
|
||||
// are shared.
|
||||
int share_depth;
|
||||
|
||||
NodeStack stack;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
void SetCordBtreeExhaustiveValidation(bool do_exaustive_validation) {
|
||||
cord_btree_exhaustive_validation.store(do_exaustive_validation,
|
||||
std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
bool IsCordBtreeExhaustiveValidationEnabled() {
|
||||
return cord_btree_exhaustive_validation.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void CordRepBtree::Dump(const CordRep* rep, absl::string_view label,
|
||||
bool include_contents, std::ostream& stream) {
|
||||
stream << "===================================\n";
|
||||
if (!label.empty()) {
|
||||
stream << label << '\n';
|
||||
stream << "-----------------------------------\n";
|
||||
}
|
||||
if (rep) {
|
||||
DumpAll(rep, include_contents, stream);
|
||||
} else {
|
||||
stream << "NULL\n";
|
||||
}
|
||||
}
|
||||
|
||||
void CordRepBtree::Dump(const CordRep* rep, absl::string_view label,
|
||||
std::ostream& stream) {
|
||||
Dump(rep, label, false, stream);
|
||||
}
|
||||
|
||||
void CordRepBtree::Dump(const CordRep* rep, std::ostream& stream) {
|
||||
Dump(rep, absl::string_view(), false, stream);
|
||||
}
|
||||
|
||||
template <size_t size>
|
||||
static void DestroyTree(CordRepBtree* tree) {
|
||||
for (CordRep* node : tree->Edges()) {
|
||||
if (node->refcount.Decrement()) continue;
|
||||
for (CordRep* edge : node->btree()->Edges()) {
|
||||
if (edge->refcount.Decrement()) continue;
|
||||
if (size == 1) {
|
||||
DeleteLeafEdge(edge);
|
||||
} else {
|
||||
CordRepBtree::Destroy(edge->btree());
|
||||
}
|
||||
}
|
||||
CordRepBtree::Delete(node->btree());
|
||||
}
|
||||
CordRepBtree::Delete(tree);
|
||||
}
|
||||
|
||||
void CordRepBtree::Destroy(CordRepBtree* tree) {
|
||||
switch (tree->height()) {
|
||||
case 0:
|
||||
for (CordRep* edge : tree->Edges()) {
|
||||
if (!edge->refcount.Decrement()) {
|
||||
DeleteLeafEdge(edge);
|
||||
}
|
||||
}
|
||||
return CordRepBtree::Delete(tree);
|
||||
case 1:
|
||||
return DestroyTree<1>(tree);
|
||||
default:
|
||||
return DestroyTree<2>(tree);
|
||||
}
|
||||
}
|
||||
|
||||
bool CordRepBtree::IsValid(const CordRepBtree* tree, bool shallow) {
|
||||
#define NODE_CHECK_VALID(x) \
|
||||
if (!(x)) { \
|
||||
ABSL_RAW_LOG(ERROR, "CordRepBtree::CheckValid() FAILED: %s", #x); \
|
||||
return false; \
|
||||
}
|
||||
#define NODE_CHECK_EQ(x, y) \
|
||||
if ((x) != (y)) { \
|
||||
ABSL_RAW_LOG(ERROR, \
|
||||
"CordRepBtree::CheckValid() FAILED: %s != %s (%s vs %s)", #x, \
|
||||
#y, absl::StrCat(x).c_str(), absl::StrCat(y).c_str()); \
|
||||
return false; \
|
||||
}
|
||||
|
||||
NODE_CHECK_VALID(tree != nullptr);
|
||||
NODE_CHECK_VALID(tree->IsBtree());
|
||||
NODE_CHECK_VALID(tree->height() <= kMaxHeight);
|
||||
NODE_CHECK_VALID(tree->begin() < tree->capacity());
|
||||
NODE_CHECK_VALID(tree->end() <= tree->capacity());
|
||||
NODE_CHECK_VALID(tree->begin() <= tree->end());
|
||||
size_t child_length = 0;
|
||||
for (CordRep* edge : tree->Edges()) {
|
||||
NODE_CHECK_VALID(edge != nullptr);
|
||||
if (tree->height() > 0) {
|
||||
NODE_CHECK_VALID(edge->IsBtree());
|
||||
NODE_CHECK_VALID(edge->btree()->height() == tree->height() - 1);
|
||||
} else {
|
||||
NODE_CHECK_VALID(IsDataEdge(edge));
|
||||
}
|
||||
child_length += edge->length;
|
||||
}
|
||||
NODE_CHECK_EQ(child_length, tree->length);
|
||||
if ((!shallow || IsCordBtreeExhaustiveValidationEnabled()) &&
|
||||
tree->height() > 0) {
|
||||
for (CordRep* edge : tree->Edges()) {
|
||||
if (!IsValid(edge->btree(), shallow)) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
#undef NODE_CHECK_VALID
|
||||
#undef NODE_CHECK_EQ
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
|
||||
CordRepBtree* CordRepBtree::AssertValid(CordRepBtree* tree, bool shallow) {
|
||||
if (!IsValid(tree, shallow)) {
|
||||
Dump(tree, "CordRepBtree validation failed:", false, std::cout);
|
||||
ABSL_RAW_LOG(FATAL, "CordRepBtree::CheckValid() FAILED");
|
||||
}
|
||||
return tree;
|
||||
}
|
||||
|
||||
const CordRepBtree* CordRepBtree::AssertValid(const CordRepBtree* tree,
|
||||
bool shallow) {
|
||||
if (!IsValid(tree, shallow)) {
|
||||
Dump(tree, "CordRepBtree validation failed:", false, std::cout);
|
||||
ABSL_RAW_LOG(FATAL, "CordRepBtree::CheckValid() FAILED");
|
||||
}
|
||||
return tree;
|
||||
}
|
||||
|
||||
#endif // NDEBUG
|
||||
|
||||
template <EdgeType edge_type>
|
||||
inline OpResult CordRepBtree::AddEdge(bool owned, CordRep* edge, size_t delta) {
|
||||
if (size() >= kMaxCapacity) return {New(edge), kPopped};
|
||||
OpResult result = ToOpResult(owned);
|
||||
result.tree->Add<edge_type>(edge);
|
||||
result.tree->length += delta;
|
||||
return result;
|
||||
}
|
||||
|
||||
template <EdgeType edge_type>
|
||||
OpResult CordRepBtree::SetEdge(bool owned, CordRep* edge, size_t delta) {
|
||||
OpResult result;
|
||||
const size_t idx = index(edge_type);
|
||||
if (owned) {
|
||||
result = {this, kSelf};
|
||||
CordRep::Unref(edges_[idx]);
|
||||
} else {
|
||||
// Create a copy containing all unchanged edges. Unchanged edges are the
|
||||
// open interval [begin, back) or [begin + 1, end) depending on `edge_type`.
|
||||
// We conveniently cover both case using a constexpr `shift` being 0 or 1
|
||||
// as `end :== back + 1`.
|
||||
result = {CopyRaw(length), kCopied};
|
||||
constexpr int shift = edge_type == kFront ? 1 : 0;
|
||||
for (CordRep* r : Edges(begin() + shift, back() + shift)) {
|
||||
CordRep::Ref(r);
|
||||
}
|
||||
}
|
||||
result.tree->edges_[idx] = edge;
|
||||
result.tree->length += delta;
|
||||
return result;
|
||||
}
|
||||
|
||||
template <EdgeType edge_type>
|
||||
CordRepBtree* CordRepBtree::AddCordRep(CordRepBtree* tree, CordRep* rep) {
|
||||
const int depth = tree->height();
|
||||
const size_t length = rep->length;
|
||||
StackOperations<edge_type> ops;
|
||||
CordRepBtree* leaf = ops.BuildStack(tree, depth);
|
||||
const OpResult result =
|
||||
leaf->AddEdge<edge_type>(ops.owned(depth), rep, length);
|
||||
return ops.Unwind(tree, depth, length, result);
|
||||
}
|
||||
|
||||
template <>
|
||||
CordRepBtree* CordRepBtree::NewLeaf<kBack>(absl::string_view data,
|
||||
size_t extra) {
|
||||
CordRepBtree* leaf = CordRepBtree::New(0);
|
||||
size_t length = 0;
|
||||
size_t end = 0;
|
||||
const size_t cap = leaf->capacity();
|
||||
while (!data.empty() && end != cap) {
|
||||
auto* flat = CordRepFlat::New(data.length() + extra);
|
||||
flat->length = (std::min)(data.length(), flat->Capacity());
|
||||
length += flat->length;
|
||||
leaf->edges_[end++] = flat;
|
||||
data = Consume<kBack>(flat->Data(), data, flat->length);
|
||||
}
|
||||
leaf->length = length;
|
||||
leaf->set_end(end);
|
||||
return leaf;
|
||||
}
|
||||
|
||||
template <>
|
||||
CordRepBtree* CordRepBtree::NewLeaf<kFront>(absl::string_view data,
|
||||
size_t extra) {
|
||||
CordRepBtree* leaf = CordRepBtree::New(0);
|
||||
size_t length = 0;
|
||||
size_t begin = leaf->capacity();
|
||||
leaf->set_end(leaf->capacity());
|
||||
while (!data.empty() && begin != 0) {
|
||||
auto* flat = CordRepFlat::New(data.length() + extra);
|
||||
flat->length = (std::min)(data.length(), flat->Capacity());
|
||||
length += flat->length;
|
||||
leaf->edges_[--begin] = flat;
|
||||
data = Consume<kFront>(flat->Data(), data, flat->length);
|
||||
}
|
||||
leaf->length = length;
|
||||
leaf->set_begin(begin);
|
||||
return leaf;
|
||||
}
|
||||
|
||||
template <>
|
||||
absl::string_view CordRepBtree::AddData<kBack>(absl::string_view data,
|
||||
size_t extra) {
|
||||
assert(!data.empty());
|
||||
assert(size() < capacity());
|
||||
AlignBegin();
|
||||
const size_t cap = capacity();
|
||||
do {
|
||||
CordRepFlat* flat = CordRepFlat::New(data.length() + extra);
|
||||
const size_t n = (std::min)(data.length(), flat->Capacity());
|
||||
flat->length = n;
|
||||
edges_[fetch_add_end(1)] = flat;
|
||||
data = Consume<kBack>(flat->Data(), data, n);
|
||||
} while (!data.empty() && end() != cap);
|
||||
return data;
|
||||
}
|
||||
|
||||
template <>
|
||||
absl::string_view CordRepBtree::AddData<kFront>(absl::string_view data,
|
||||
size_t extra) {
|
||||
assert(!data.empty());
|
||||
assert(size() < capacity());
|
||||
AlignEnd();
|
||||
do {
|
||||
CordRepFlat* flat = CordRepFlat::New(data.length() + extra);
|
||||
const size_t n = (std::min)(data.length(), flat->Capacity());
|
||||
flat->length = n;
|
||||
edges_[sub_fetch_begin(1)] = flat;
|
||||
data = Consume<kFront>(flat->Data(), data, n);
|
||||
} while (!data.empty() && begin() != 0);
|
||||
return data;
|
||||
}
|
||||
|
||||
template <EdgeType edge_type>
|
||||
CordRepBtree* CordRepBtree::AddData(CordRepBtree* tree, absl::string_view data,
|
||||
size_t extra) {
|
||||
if (ABSL_PREDICT_FALSE(data.empty())) return tree;
|
||||
|
||||
const size_t original_data_size = data.size();
|
||||
int depth = tree->height();
|
||||
StackOperations<edge_type> ops;
|
||||
CordRepBtree* leaf = ops.BuildStack(tree, depth);
|
||||
|
||||
// If there is capacity in the last edge, append as much data
|
||||
// as possible into this last edge.
|
||||
if (leaf->size() < leaf->capacity()) {
|
||||
OpResult result = leaf->ToOpResult(ops.owned(depth));
|
||||
data = result.tree->AddData<edge_type>(data, extra);
|
||||
if (data.empty()) {
|
||||
result.tree->length += original_data_size;
|
||||
return ops.Unwind(tree, depth, original_data_size, result);
|
||||
}
|
||||
|
||||
// We added some data into this leaf, but not all. Propagate the added
|
||||
// length to the top most node, and rebuild the stack with any newly copied
|
||||
// or updated nodes. From this point on, the path (leg) from the top most
|
||||
// node to the right-most node towards the leaf node is privately owned.
|
||||
size_t delta = original_data_size - data.size();
|
||||
assert(delta > 0);
|
||||
result.tree->length += delta;
|
||||
tree = ops.Propagate(tree, depth, delta, result);
|
||||
ops.share_depth = depth + 1;
|
||||
}
|
||||
|
||||
// We were unable to append all data into the existing right-most leaf node.
|
||||
// This means all remaining data must be put into (a) new leaf node(s) which
|
||||
// we append to the tree. To make this efficient, we iteratively build full
|
||||
// leaf nodes from `data` until the created leaf contains all remaining data.
|
||||
// We utilize the `Unwind` method to merge the created leaf into the first
|
||||
// level towards root that has capacity. On each iteration with remaining
|
||||
// data, we rebuild the stack in the knowledge that right-most nodes are
|
||||
// privately owned after the first `Unwind` completes.
|
||||
for (;;) {
|
||||
OpResult result = {CordRepBtree::NewLeaf<edge_type>(data, extra), kPopped};
|
||||
if (result.tree->length == data.size()) {
|
||||
return ops.Unwind(tree, depth, result.tree->length, result);
|
||||
}
|
||||
data = Consume<edge_type>(data, result.tree->length);
|
||||
tree = ops.Unwind(tree, depth, result.tree->length, result);
|
||||
depth = tree->height();
|
||||
ops.BuildOwnedStack(tree, depth);
|
||||
}
|
||||
}
|
||||
|
||||
template <EdgeType edge_type>
|
||||
CordRepBtree* CordRepBtree::Merge(CordRepBtree* dst, CordRepBtree* src) {
|
||||
assert(dst->height() >= src->height());
|
||||
|
||||
// Capture source length as we may consume / destroy `src`.
|
||||
const size_t length = src->length;
|
||||
|
||||
// We attempt to merge `src` at its corresponding height in `dst`.
|
||||
const int depth = dst->height() - src->height();
|
||||
StackOperations<edge_type> ops;
|
||||
CordRepBtree* merge_node = ops.BuildStack(dst, depth);
|
||||
|
||||
// If there is enough space in `merge_node` for all edges from `src`, add all
|
||||
// edges to this node, making a fresh copy as needed if not privately owned.
|
||||
// If `merge_node` does not have capacity for `src`, we rely on `Unwind` and
|
||||
// `Finalize` to merge `src` into the first level towards `root` where there
|
||||
// is capacity for another edge, or create a new top level node.
|
||||
OpResult result;
|
||||
if (merge_node->size() + src->size() <= kMaxCapacity) {
|
||||
result = merge_node->ToOpResult(ops.owned(depth));
|
||||
result.tree->Add<edge_type>(src->Edges());
|
||||
result.tree->length += src->length;
|
||||
if (src->refcount.IsOne()) {
|
||||
Delete(src);
|
||||
} else {
|
||||
for (CordRep* edge : src->Edges()) CordRep::Ref(edge);
|
||||
CordRepBtree::Unref(src);
|
||||
}
|
||||
} else {
|
||||
result = {src, kPopped};
|
||||
}
|
||||
|
||||
// Unless we merged at the top level (i.e.: src and dst are equal height),
|
||||
// unwind the result towards the top level, and finalize the result.
|
||||
if (depth) {
|
||||
return ops.Unwind(dst, depth, length, result);
|
||||
}
|
||||
return ops.Finalize(dst, result);
|
||||
}
|
||||
|
||||
CopyResult CordRepBtree::CopySuffix(size_t offset) {
|
||||
assert(offset < this->length);
|
||||
|
||||
// As long as `offset` starts inside the last edge, we can 'drop' the current
|
||||
// depth. For the most extreme example: if offset references the last data
|
||||
// edge in the tree, there is only a single edge / path from the top of the
|
||||
// tree to that last edge, so we can drop all the nodes except that edge.
|
||||
// The fast path check for this is `back->length >= length - offset`.
|
||||
int height = this->height();
|
||||
CordRepBtree* node = this;
|
||||
size_t len = node->length - offset;
|
||||
CordRep* back = node->Edge(kBack);
|
||||
while (back->length >= len) {
|
||||
offset = back->length - len;
|
||||
if (--height < 0) {
|
||||
return {MakeSubstring(CordRep::Ref(back), offset), height};
|
||||
}
|
||||
node = back->btree();
|
||||
back = node->Edge(kBack);
|
||||
}
|
||||
if (offset == 0) return {CordRep::Ref(node), height};
|
||||
|
||||
// Offset does not point into the last edge, so we span at least two edges.
|
||||
// Find the index of offset with `IndexBeyond` which provides us the edge
|
||||
// 'beyond' the offset if offset is not a clean starting point of an edge.
|
||||
Position pos = node->IndexBeyond(offset);
|
||||
CordRepBtree* sub = node->CopyToEndFrom(pos.index, len);
|
||||
const CopyResult result = {sub, height};
|
||||
|
||||
// `pos.n` contains a non zero value if the offset is not an exact starting
|
||||
// point of an edge. In this case, `pos.n` contains the 'trailing' amount of
|
||||
// bytes of the edge preceding that in `pos.index`. We need to iteratively
|
||||
// adjust the preceding edge with the 'broken' offset until we have a perfect
|
||||
// start of the edge.
|
||||
while (pos.n != 0) {
|
||||
assert(pos.index >= 1);
|
||||
const size_t begin = pos.index - 1;
|
||||
sub->set_begin(begin);
|
||||
CordRep* const edge = node->Edge(begin);
|
||||
|
||||
len = pos.n;
|
||||
offset = edge->length - len;
|
||||
|
||||
if (--height < 0) {
|
||||
sub->edges_[begin] = MakeSubstring(CordRep::Ref(edge), offset, len);
|
||||
return result;
|
||||
}
|
||||
|
||||
node = edge->btree();
|
||||
pos = node->IndexBeyond(offset);
|
||||
|
||||
CordRepBtree* nsub = node->CopyToEndFrom(pos.index, len);
|
||||
sub->edges_[begin] = nsub;
|
||||
sub = nsub;
|
||||
}
|
||||
sub->set_begin(pos.index);
|
||||
return result;
|
||||
}
|
||||
|
||||
CopyResult CordRepBtree::CopyPrefix(size_t n, bool allow_folding) {
|
||||
assert(n > 0);
|
||||
assert(n <= this->length);
|
||||
|
||||
// As long as `n` does not exceed the length of the first edge, we can 'drop'
|
||||
// the current depth. For the most extreme example: if we'd copy a 1 byte
|
||||
// prefix from a tree, there is only a single edge / path from the top of the
|
||||
// tree to the single data edge containing this byte, so we can drop all the
|
||||
// nodes except the data node.
|
||||
int height = this->height();
|
||||
CordRepBtree* node = this;
|
||||
CordRep* front = node->Edge(kFront);
|
||||
if (allow_folding) {
|
||||
while (front->length >= n) {
|
||||
if (--height < 0) return {MakeSubstring(CordRep::Ref(front), 0, n), -1};
|
||||
node = front->btree();
|
||||
front = node->Edge(kFront);
|
||||
}
|
||||
}
|
||||
if (node->length == n) return {CordRep::Ref(node), height};
|
||||
|
||||
// `n` spans at least two nodes, find the end point of the span.
|
||||
Position pos = node->IndexOf(n);
|
||||
|
||||
// Create a partial copy of the node up to `pos.index`, with a defined length
|
||||
// of `n`. Any 'partial last edge' is added further below as needed.
|
||||
CordRepBtree* sub = node->CopyBeginTo(pos.index, n);
|
||||
const CopyResult result = {sub, height};
|
||||
|
||||
// `pos.n` contains the 'offset inside the edge for IndexOf(n)'. As long as
|
||||
// this is not zero, we don't have a 'clean cut', so we need to make a
|
||||
// (partial) copy of that last edge, and repeat this until pos.n is zero.
|
||||
while (pos.n != 0) {
|
||||
size_t end = pos.index;
|
||||
n = pos.n;
|
||||
|
||||
CordRep* edge = node->Edge(pos.index);
|
||||
if (--height < 0) {
|
||||
sub->edges_[end++] = MakeSubstring(CordRep::Ref(edge), 0, n);
|
||||
sub->set_end(end);
|
||||
AssertValid(result.edge->btree());
|
||||
return result;
|
||||
}
|
||||
|
||||
node = edge->btree();
|
||||
pos = node->IndexOf(n);
|
||||
CordRepBtree* nsub = node->CopyBeginTo(pos.index, n);
|
||||
sub->edges_[end++] = nsub;
|
||||
sub->set_end(end);
|
||||
sub = nsub;
|
||||
}
|
||||
sub->set_end(pos.index);
|
||||
AssertValid(result.edge->btree());
|
||||
return result;
|
||||
}
|
||||
|
||||
CordRep* CordRepBtree::ExtractFront(CordRepBtree* tree) {
|
||||
CordRep* front = tree->Edge(tree->begin());
|
||||
if (tree->refcount.IsOne()) {
|
||||
Unref(tree->Edges(tree->begin() + 1, tree->end()));
|
||||
CordRepBtree::Delete(tree);
|
||||
} else {
|
||||
CordRep::Ref(front);
|
||||
CordRep::Unref(tree);
|
||||
}
|
||||
return front;
|
||||
}
|
||||
|
||||
CordRepBtree* CordRepBtree::ConsumeBeginTo(CordRepBtree* tree, size_t end,
|
||||
size_t new_length) {
|
||||
assert(end <= tree->end());
|
||||
if (tree->refcount.IsOne()) {
|
||||
Unref(tree->Edges(end, tree->end()));
|
||||
tree->set_end(end);
|
||||
tree->length = new_length;
|
||||
} else {
|
||||
CordRepBtree* old = tree;
|
||||
tree = tree->CopyBeginTo(end, new_length);
|
||||
CordRep::Unref(old);
|
||||
}
|
||||
return tree;
|
||||
}
|
||||
|
||||
CordRep* CordRepBtree::RemoveSuffix(CordRepBtree* tree, size_t n) {
|
||||
// Check input and deal with trivial cases 'Remove all/none'
|
||||
assert(tree != nullptr);
|
||||
assert(n <= tree->length);
|
||||
const size_t len = tree->length;
|
||||
if (ABSL_PREDICT_FALSE(n == 0)) {
|
||||
return tree;
|
||||
}
|
||||
if (ABSL_PREDICT_FALSE(n >= len)) {
|
||||
CordRepBtree::Unref(tree);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
size_t length = len - n;
|
||||
int height = tree->height();
|
||||
bool is_mutable = tree->refcount.IsOne();
|
||||
|
||||
// Extract all top nodes which are reduced to size = 1
|
||||
Position pos = tree->IndexOfLength(length);
|
||||
while (pos.index == tree->begin()) {
|
||||
CordRep* edge = ExtractFront(tree);
|
||||
is_mutable &= edge->refcount.IsOne();
|
||||
if (height-- == 0) return ResizeEdge(edge, length, is_mutable);
|
||||
tree = edge->btree();
|
||||
pos = tree->IndexOfLength(length);
|
||||
}
|
||||
|
||||
// Repeat the following sequence traversing down the tree:
|
||||
// - Crop the top node to the 'last remaining edge' adjusting length.
|
||||
// - Set the length for down edges to the partial length in that last edge.
|
||||
// - Repeat this until the last edge is 'included in full'
|
||||
// - If we hit the data edge level, resize and return the last data edge
|
||||
CordRepBtree* top = tree = ConsumeBeginTo(tree, pos.index + 1, length);
|
||||
CordRep* edge = tree->Edge(pos.index);
|
||||
length = pos.n;
|
||||
while (length != edge->length) {
|
||||
// ConsumeBeginTo guarantees `tree` is a clean, privately owned copy.
|
||||
assert(tree->refcount.IsOne());
|
||||
const bool edge_is_mutable = edge->refcount.IsOne();
|
||||
|
||||
if (height-- == 0) {
|
||||
tree->edges_[pos.index] = ResizeEdge(edge, length, edge_is_mutable);
|
||||
return AssertValid(top);
|
||||
}
|
||||
|
||||
if (!edge_is_mutable) {
|
||||
// We can't 'in place' remove any suffixes down this edge.
|
||||
// Replace this edge with a prefix copy instead.
|
||||
tree->edges_[pos.index] = edge->btree()->CopyPrefix(length, false).edge;
|
||||
CordRep::Unref(edge);
|
||||
return AssertValid(top);
|
||||
}
|
||||
|
||||
// Move down one level, rinse repeat.
|
||||
tree = edge->btree();
|
||||
pos = tree->IndexOfLength(length);
|
||||
tree = ConsumeBeginTo(edge->btree(), pos.index + 1, length);
|
||||
edge = tree->Edge(pos.index);
|
||||
length = pos.n;
|
||||
}
|
||||
|
||||
return AssertValid(top);
|
||||
}
|
||||
|
||||
CordRep* CordRepBtree::SubTree(size_t offset, size_t n) {
|
||||
assert(n <= this->length);
|
||||
assert(offset <= this->length - n);
|
||||
if (ABSL_PREDICT_FALSE(n == 0)) return nullptr;
|
||||
|
||||
CordRepBtree* node = this;
|
||||
int height = node->height();
|
||||
Position front = node->IndexOf(offset);
|
||||
CordRep* left = node->edges_[front.index];
|
||||
while (front.n + n <= left->length) {
|
||||
if (--height < 0) return MakeSubstring(CordRep::Ref(left), front.n, n);
|
||||
node = left->btree();
|
||||
front = node->IndexOf(front.n);
|
||||
left = node->edges_[front.index];
|
||||
}
|
||||
|
||||
const Position back = node->IndexBefore(front, n);
|
||||
CordRep* const right = node->edges_[back.index];
|
||||
assert(back.index > front.index);
|
||||
|
||||
// Get partial suffix and prefix entries.
|
||||
CopyResult prefix;
|
||||
CopyResult suffix;
|
||||
if (height > 0) {
|
||||
// Copy prefix and suffix of the boundary nodes.
|
||||
prefix = left->btree()->CopySuffix(front.n);
|
||||
suffix = right->btree()->CopyPrefix(back.n);
|
||||
|
||||
// If there is an edge between the prefix and suffix edges, then the tree
|
||||
// must remain at its previous (full) height. If we have no edges between
|
||||
// prefix and suffix edges, then the tree must be as high as either the
|
||||
// suffix or prefix edges (which are collapsed to their minimum heights).
|
||||
if (front.index + 1 == back.index) {
|
||||
height = (std::max)(prefix.height, suffix.height) + 1;
|
||||
}
|
||||
|
||||
// Raise prefix and suffixes to the new tree height.
|
||||
for (int h = prefix.height + 1; h < height; ++h) {
|
||||
prefix.edge = CordRepBtree::New(prefix.edge);
|
||||
}
|
||||
for (int h = suffix.height + 1; h < height; ++h) {
|
||||
suffix.edge = CordRepBtree::New(suffix.edge);
|
||||
}
|
||||
} else {
|
||||
// Leaf node, simply take substrings for prefix and suffix.
|
||||
prefix = CopyResult{MakeSubstring(CordRep::Ref(left), front.n), -1};
|
||||
suffix = CopyResult{MakeSubstring(CordRep::Ref(right), 0, back.n), -1};
|
||||
}
|
||||
|
||||
// Compose resulting tree.
|
||||
CordRepBtree* sub = CordRepBtree::New(height);
|
||||
size_t end = 0;
|
||||
sub->edges_[end++] = prefix.edge;
|
||||
for (CordRep* r : node->Edges(front.index + 1, back.index)) {
|
||||
sub->edges_[end++] = CordRep::Ref(r);
|
||||
}
|
||||
sub->edges_[end++] = suffix.edge;
|
||||
sub->set_end(end);
|
||||
sub->length = n;
|
||||
return AssertValid(sub);
|
||||
}
|
||||
|
||||
CordRepBtree* CordRepBtree::MergeTrees(CordRepBtree* left,
|
||||
CordRepBtree* right) {
|
||||
return left->height() >= right->height() ? Merge<kBack>(left, right)
|
||||
: Merge<kFront>(right, left);
|
||||
}
|
||||
|
||||
bool CordRepBtree::IsFlat(absl::string_view* fragment) const {
|
||||
if (height() == 0 && size() == 1) {
|
||||
if (fragment) *fragment = Data(begin());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CordRepBtree::IsFlat(size_t offset, const size_t n,
|
||||
absl::string_view* fragment) const {
|
||||
assert(n <= this->length);
|
||||
assert(offset <= this->length - n);
|
||||
if (ABSL_PREDICT_FALSE(n == 0)) return false;
|
||||
int height = this->height();
|
||||
const CordRepBtree* node = this;
|
||||
for (;;) {
|
||||
const Position front = node->IndexOf(offset);
|
||||
const CordRep* edge = node->Edge(front.index);
|
||||
if (edge->length < front.n + n) return false;
|
||||
if (--height < 0) {
|
||||
if (fragment) *fragment = EdgeData(edge).substr(front.n, n);
|
||||
return true;
|
||||
}
|
||||
offset = front.n;
|
||||
node = node->Edge(front.index)->btree();
|
||||
}
|
||||
}
|
||||
|
||||
char CordRepBtree::GetCharacter(size_t offset) const {
|
||||
assert(offset < length);
|
||||
const CordRepBtree* node = this;
|
||||
int height = node->height();
|
||||
for (;;) {
|
||||
Position front = node->IndexOf(offset);
|
||||
if (--height < 0) return node->Data(front.index)[front.n];
|
||||
offset = front.n;
|
||||
node = node->Edge(front.index)->btree();
|
||||
}
|
||||
}
|
||||
|
||||
Span<char> CordRepBtree::GetAppendBufferSlow(size_t size) {
|
||||
// The inlined version in `GetAppendBuffer()` deals with all heights <= 3.
|
||||
assert(height() >= 4);
|
||||
assert(refcount.IsOne());
|
||||
|
||||
// Build a stack of nodes we may potentially need to update if we find a
|
||||
// non-shared FLAT with capacity at the leaf level.
|
||||
const int depth = height();
|
||||
CordRepBtree* node = this;
|
||||
CordRepBtree* stack[kMaxDepth];
|
||||
for (int i = 0; i < depth; ++i) {
|
||||
node = node->Edge(kBack)->btree();
|
||||
if (!node->refcount.IsOne()) return {};
|
||||
stack[i] = node;
|
||||
}
|
||||
|
||||
// Must be a privately owned, mutable flat.
|
||||
CordRep* const edge = node->Edge(kBack);
|
||||
if (!edge->refcount.IsOne() || edge->tag < FLAT) return {};
|
||||
|
||||
// Must have capacity.
|
||||
const size_t avail = edge->flat()->Capacity() - edge->length;
|
||||
if (avail == 0) return {};
|
||||
|
||||
// Build span on remaining capacity.
|
||||
size_t delta = (std::min)(size, avail);
|
||||
Span<char> span = {edge->flat()->Data() + edge->length, delta};
|
||||
edge->length += delta;
|
||||
this->length += delta;
|
||||
for (int i = 0; i < depth; ++i) {
|
||||
stack[i]->length += delta;
|
||||
}
|
||||
return span;
|
||||
}
|
||||
|
||||
CordRepBtree* CordRepBtree::CreateSlow(CordRep* rep) {
|
||||
if (rep->IsBtree()) return rep->btree();
|
||||
|
||||
CordRepBtree* node = nullptr;
|
||||
auto consume = [&node](CordRep* r, size_t offset, size_t length) {
|
||||
r = MakeSubstring(r, offset, length);
|
||||
if (node == nullptr) {
|
||||
node = New(r);
|
||||
} else {
|
||||
node = CordRepBtree::AddCordRep<kBack>(node, r);
|
||||
}
|
||||
};
|
||||
Consume(rep, consume);
|
||||
return node;
|
||||
}
|
||||
|
||||
CordRepBtree* CordRepBtree::AppendSlow(CordRepBtree* tree, CordRep* rep) {
|
||||
if (ABSL_PREDICT_TRUE(rep->IsBtree())) {
|
||||
return MergeTrees(tree, rep->btree());
|
||||
}
|
||||
auto consume = [&tree](CordRep* r, size_t offset, size_t length) {
|
||||
r = MakeSubstring(r, offset, length);
|
||||
tree = CordRepBtree::AddCordRep<kBack>(tree, r);
|
||||
};
|
||||
Consume(rep, consume);
|
||||
return tree;
|
||||
}
|
||||
|
||||
CordRepBtree* CordRepBtree::PrependSlow(CordRepBtree* tree, CordRep* rep) {
|
||||
if (ABSL_PREDICT_TRUE(rep->IsBtree())) {
|
||||
return MergeTrees(rep->btree(), tree);
|
||||
}
|
||||
auto consume = [&tree](CordRep* r, size_t offset, size_t length) {
|
||||
r = MakeSubstring(r, offset, length);
|
||||
tree = CordRepBtree::AddCordRep<kFront>(tree, r);
|
||||
};
|
||||
ReverseConsume(rep, consume);
|
||||
return tree;
|
||||
}
|
||||
|
||||
CordRepBtree* CordRepBtree::Append(CordRepBtree* tree, absl::string_view data,
|
||||
size_t extra) {
|
||||
return CordRepBtree::AddData<kBack>(tree, data, extra);
|
||||
}
|
||||
|
||||
CordRepBtree* CordRepBtree::Prepend(CordRepBtree* tree, absl::string_view data,
|
||||
size_t extra) {
|
||||
return CordRepBtree::AddData<kFront>(tree, data, extra);
|
||||
}
|
||||
|
||||
template CordRepBtree* CordRepBtree::AddCordRep<kFront>(CordRepBtree* tree,
|
||||
CordRep* rep);
|
||||
template CordRepBtree* CordRepBtree::AddCordRep<kBack>(CordRepBtree* tree,
|
||||
CordRep* rep);
|
||||
template CordRepBtree* CordRepBtree::AddData<kFront>(CordRepBtree* tree,
|
||||
absl::string_view data,
|
||||
size_t extra);
|
||||
template CordRepBtree* CordRepBtree::AddData<kBack>(CordRepBtree* tree,
|
||||
absl::string_view data,
|
||||
size_t extra);
|
||||
|
||||
void CordRepBtree::Rebuild(CordRepBtree** stack, CordRepBtree* tree,
|
||||
bool consume) {
|
||||
bool owned = consume && tree->refcount.IsOne();
|
||||
if (tree->height() == 0) {
|
||||
for (CordRep* edge : tree->Edges()) {
|
||||
if (!owned) edge = CordRep::Ref(edge);
|
||||
size_t height = 0;
|
||||
size_t length = edge->length;
|
||||
CordRepBtree* node = stack[0];
|
||||
OpResult result = node->AddEdge<kBack>(true, edge, length);
|
||||
while (result.action == CordRepBtree::kPopped) {
|
||||
stack[height] = result.tree;
|
||||
if (stack[++height] == nullptr) {
|
||||
result.action = CordRepBtree::kSelf;
|
||||
stack[height] = CordRepBtree::New(node, result.tree);
|
||||
} else {
|
||||
node = stack[height];
|
||||
result = node->AddEdge<kBack>(true, result.tree, length);
|
||||
}
|
||||
}
|
||||
while (stack[++height] != nullptr) {
|
||||
stack[height]->length += length;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (CordRep* rep : tree->Edges()) {
|
||||
Rebuild(stack, rep->btree(), owned);
|
||||
}
|
||||
}
|
||||
if (consume) {
|
||||
if (owned) {
|
||||
CordRepBtree::Delete(tree);
|
||||
} else {
|
||||
CordRepBtree::Unref(tree);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CordRepBtree* CordRepBtree::Rebuild(CordRepBtree* tree) {
|
||||
// Set up initial stack with empty leaf node.
|
||||
CordRepBtree* node = CordRepBtree::New();
|
||||
CordRepBtree* stack[CordRepBtree::kMaxDepth + 1] = {node};
|
||||
|
||||
// Recursively build the tree, consuming the input tree.
|
||||
Rebuild(stack, tree, /* consume reference */ true);
|
||||
|
||||
// Return top most node
|
||||
for (CordRepBtree* parent : stack) {
|
||||
if (parent == nullptr) return node;
|
||||
node = parent;
|
||||
}
|
||||
|
||||
// Unreachable
|
||||
assert(false);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CordRepBtree::ExtractResult CordRepBtree::ExtractAppendBuffer(
|
||||
CordRepBtree* tree, size_t extra_capacity) {
|
||||
int depth = 0;
|
||||
NodeStack stack;
|
||||
|
||||
// Set up default 'no success' result which is {tree, nullptr}.
|
||||
ExtractResult result;
|
||||
result.tree = tree;
|
||||
result.extracted = nullptr;
|
||||
|
||||
// Dive down the right side of the tree, making sure no edges are shared.
|
||||
while (tree->height() > 0) {
|
||||
if (!tree->refcount.IsOne()) return result;
|
||||
stack[depth++] = tree;
|
||||
tree = tree->Edge(kBack)->btree();
|
||||
}
|
||||
if (!tree->refcount.IsOne()) return result;
|
||||
|
||||
// Validate we ended on a non shared flat.
|
||||
CordRep* rep = tree->Edge(kBack);
|
||||
if (!(rep->IsFlat() && rep->refcount.IsOne())) return result;
|
||||
|
||||
// Verify it has at least the requested extra capacity.
|
||||
CordRepFlat* flat = rep->flat();
|
||||
const size_t length = flat->length;
|
||||
const size_t avail = flat->Capacity() - flat->length;
|
||||
if (extra_capacity > avail) return result;
|
||||
|
||||
// Set the extracted flat in the result.
|
||||
result.extracted = flat;
|
||||
|
||||
// Cascading delete all nodes that become empty.
|
||||
while (tree->size() == 1) {
|
||||
CordRepBtree::Delete(tree);
|
||||
if (--depth < 0) {
|
||||
// We consumed the entire tree: return nullptr for new tree.
|
||||
result.tree = nullptr;
|
||||
return result;
|
||||
}
|
||||
rep = tree;
|
||||
tree = stack[depth];
|
||||
}
|
||||
|
||||
// Remove the edge or cascaded up parent node.
|
||||
tree->set_end(tree->end() - 1);
|
||||
tree->length -= length;
|
||||
|
||||
// Adjust lengths up the tree.
|
||||
while (depth > 0) {
|
||||
tree = stack[--depth];
|
||||
tree->length -= length;
|
||||
}
|
||||
|
||||
// Remove unnecessary top nodes with size = 1. This may iterate all the way
|
||||
// down to the leaf node in which case we simply return the remaining last
|
||||
// edge in that node and the extracted flat.
|
||||
while (tree->size() == 1) {
|
||||
int height = tree->height();
|
||||
rep = tree->Edge(kBack);
|
||||
Delete(tree);
|
||||
if (height == 0) {
|
||||
// We consumed the leaf: return the sole data edge as the new tree.
|
||||
result.tree = rep;
|
||||
return result;
|
||||
}
|
||||
tree = rep->btree();
|
||||
}
|
||||
|
||||
// Done: return the (new) top level node and extracted flat.
|
||||
result.tree = tree;
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace cord_internal
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
944
Pods/abseil/absl/strings/internal/cord_rep_btree.h
generated
Normal file
944
Pods/abseil/absl/strings/internal/cord_rep_btree.h
generated
Normal file
@@ -0,0 +1,944 @@
|
||||
// Copyright 2021 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_CORD_REP_BTREE_H_
|
||||
#define ABSL_STRINGS_INTERNAL_CORD_REP_BTREE_H_
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <iosfwd>
|
||||
|
||||
#include "absl/base/config.h"
|
||||
#include "absl/base/internal/raw_logging.h"
|
||||
#include "absl/base/optimization.h"
|
||||
#include "absl/strings/internal/cord_data_edge.h"
|
||||
#include "absl/strings/internal/cord_internal.h"
|
||||
#include "absl/strings/internal/cord_rep_flat.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "absl/types/span.h"
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace cord_internal {
|
||||
|
||||
// `SetCordBtreeExhaustiveValidation()` can be set to force exhaustive
|
||||
// validation in debug assertions, and code that calls `IsValid()`
|
||||
// explicitly. By default, assertions should be relatively cheap and
|
||||
// AssertValid() can easily lead to O(n^2) complexity as recursive / full tree
|
||||
// validation is O(n).
|
||||
void SetCordBtreeExhaustiveValidation(bool do_exaustive_validation);
|
||||
bool IsCordBtreeExhaustiveValidationEnabled();
|
||||
|
||||
class CordRepBtreeNavigator;
|
||||
|
||||
// CordRepBtree is as the name implies a btree implementation of a Cordrep tree.
|
||||
// Data is stored at the leaf level only, non leaf nodes contain down pointers
|
||||
// only. Allowed types of data edges are FLAT, EXTERNAL and SUBSTRINGs of FLAT
|
||||
// or EXTERNAL nodes. The implementation allows for data to be added to either
|
||||
// end of the tree only, it does not provide any 'insert' logic. This has the
|
||||
// benefit that we can expect good fill ratios: all nodes except the outer
|
||||
// 'legs' will have 100% fill ratios for trees built using Append/Prepend
|
||||
// methods. Merged trees will typically have a fill ratio well above 50% as in a
|
||||
// similar fashion, one side of the merged tree will typically have a 100% fill
|
||||
// ratio, and the 'open' end will average 50%. All operations are O(log(n)) or
|
||||
// better, and the tree never needs balancing.
|
||||
//
|
||||
// All methods accepting a CordRep* or CordRepBtree* adopt a reference on that
|
||||
// input unless explicitly stated otherwise. All functions returning a CordRep*
|
||||
// or CordRepBtree* instance transfer a reference back to the caller.
|
||||
// Simplified, callers both 'donate' and 'consume' a reference count on each
|
||||
// call, simplifying the API. An example of building a tree:
|
||||
//
|
||||
// CordRepBtree* tree = CordRepBtree::Create(MakeFlat("Hello"));
|
||||
// tree = CordRepBtree::Append(tree, MakeFlat("world"));
|
||||
//
|
||||
// In the above example, all inputs are consumed, making each call affecting
|
||||
// `tree` reference count neutral. The returned `tree` value can be different
|
||||
// from the input if the input is shared with other threads, or if the tree
|
||||
// grows in height, but callers typically never have to concern themselves with
|
||||
// that and trust that all methods DTRT at all times.
|
||||
class CordRepBtree : public CordRep {
|
||||
public:
|
||||
// EdgeType identifies `front` and `back` enum values.
|
||||
// Various implementations in CordRepBtree such as `Add` and `Edge` are
|
||||
// generic and templated on operating on either of the boundary edges.
|
||||
// For more information on the possible edges contained in a CordRepBtree
|
||||
// instance see the documentation for `edges_`.
|
||||
enum class EdgeType { kFront, kBack };
|
||||
|
||||
// Convenience constants into `EdgeType`
|
||||
static constexpr EdgeType kFront = EdgeType::kFront;
|
||||
static constexpr EdgeType kBack = EdgeType::kBack;
|
||||
|
||||
// Maximum number of edges: based on experiments and performance data, we can
|
||||
// pick suitable values resulting in optimum cacheline aligned values. The
|
||||
// preferred values are based on 64-bit systems where we aim to align this
|
||||
// class onto 64 bytes, i.e.: 6 = 64 bytes, 14 = 128 bytes, etc.
|
||||
// TODO(b/192061034): experiment with alternative sizes.
|
||||
static constexpr size_t kMaxCapacity = 6;
|
||||
|
||||
// Reasonable maximum height of the btree. We can expect a fill ratio of at
|
||||
// least 50%: trees are always expanded at the front or back. Concatenating
|
||||
// trees will then typically fold at the top most node, where the lower nodes
|
||||
// are at least at capacity on one side of joined inputs. At a lower fill
|
||||
// rate of 4 edges per node, we have capacity for ~16 million leaf nodes.
|
||||
// We will fail / abort if an application ever exceeds this height, which
|
||||
// should be extremely rare (near impossible) and be an indication of an
|
||||
// application error: we do not assume it reasonable for any application to
|
||||
// operate correctly with such monster trees.
|
||||
// Another compelling reason for the number `12` is that any contextual stack
|
||||
// required for navigation or insertion requires 12 words and 12 bytes, which
|
||||
// fits inside 2 cache lines with some room to spare, and is reasonable as a
|
||||
// local stack variable compared to Cord's current near 400 bytes stack use.
|
||||
// The maximum `height` value of a node is then `kMaxDepth - 1` as node height
|
||||
// values start with a value of 0 for leaf nodes.
|
||||
static constexpr size_t kMaxDepth = 12;
|
||||
// See comments on height() for why this is an int and not a size_t.
|
||||
static constexpr int kMaxHeight = static_cast<int>(kMaxDepth - 1);
|
||||
|
||||
// `Action` defines the action for unwinding changes done at the btree's leaf
|
||||
// level that need to be propagated up to the parent node(s). Each operation
|
||||
// on a node has an effect / action defined as follows:
|
||||
// - kSelf
|
||||
// The operation (add / update, etc) was performed directly on the node as
|
||||
// the node is private to the current thread (i.e.: not shared directly or
|
||||
// indirectly through a refcount > 1). Changes can be propagated directly to
|
||||
// all parent nodes as all parent nodes are also then private to the current
|
||||
// thread.
|
||||
// - kCopied
|
||||
// The operation (add / update, etc) was performed on a copy of the original
|
||||
// node, as the node is (potentially) directly or indirectly shared with
|
||||
// other threads. Changes need to be propagated into the parent nodes where
|
||||
// the old down pointer must be unreffed and replaced with this new copy.
|
||||
// Such changes to parent nodes may themselves require a copy if the parent
|
||||
// node is also shared. A kCopied action can propagate all the way to the
|
||||
// top node where we then must unref the `tree` input provided by the
|
||||
// caller, and return the new copy.
|
||||
// - kPopped
|
||||
// The operation (typically add) could not be satisfied due to insufficient
|
||||
// capacity in the targeted node, and a new 'leg' was created that needs to
|
||||
// be added into the parent node. For example, adding a FLAT inside a leaf
|
||||
// node that is at capacity will create a new leaf node containing that
|
||||
// FLAT, that needs to be 'popped' up the btree. Such 'pop' actions can
|
||||
// cascade up the tree if parent nodes are also at capacity. A 'Popped'
|
||||
// action propagating all the way to the top of the tree will result in
|
||||
// the tree becoming one level higher than the current tree through a final
|
||||
// `CordRepBtree::New(tree, popped)` call, resulting in a new top node
|
||||
// referencing the old tree and the new (fully popped upwards) 'leg'.
|
||||
enum Action { kSelf, kCopied, kPopped };
|
||||
|
||||
// Result of an operation on a node. See the `Action` enum for details.
|
||||
struct OpResult {
|
||||
CordRepBtree* tree;
|
||||
Action action;
|
||||
};
|
||||
|
||||
// Return value of the CopyPrefix and CopySuffix methods which can
|
||||
// return a node or data edge at any height inside the tree.
|
||||
// A height of 0 defines the lowest (leaf) node, a height of -1 identifies
|
||||
// `edge` as being a plain data node: EXTERNAL / FLAT or SUBSTRING thereof.
|
||||
struct CopyResult {
|
||||
CordRep* edge;
|
||||
int height;
|
||||
};
|
||||
|
||||
// Logical position inside a node:
|
||||
// - index: index of the edge.
|
||||
// - n: size or offset value depending on context.
|
||||
struct Position {
|
||||
size_t index;
|
||||
size_t n;
|
||||
};
|
||||
|
||||
// Creates a btree from the given input. Adopts a ref of `rep`.
|
||||
// If the input `rep` is itself a btree, i.e., `IsBtree()`, then this
|
||||
// function immediately returns `rep->btree()`. If the input is a valid data
|
||||
// edge (see IsDataEdge()), then a new leaf node is returned containing `rep`
|
||||
// as the sole data edge. Else, the input is assumed to be a (legacy) concat
|
||||
// tree, and the input is consumed and transformed into a btree().
|
||||
static CordRepBtree* Create(CordRep* rep);
|
||||
|
||||
// Destroys the provided tree. Should only be called by cord internal API's,
|
||||
// typically after a ref_count.Decrement() on the last reference count.
|
||||
static void Destroy(CordRepBtree* tree);
|
||||
|
||||
// Destruction
|
||||
static void Delete(CordRepBtree* tree) { delete tree; }
|
||||
|
||||
// Use CordRep::Unref() as we overload for absl::Span<CordRep* const>.
|
||||
using CordRep::Unref;
|
||||
|
||||
// Unrefs all edges in `edges` which are assumed to be 'likely one'.
|
||||
static void Unref(absl::Span<CordRep* const> edges);
|
||||
|
||||
// Appends / Prepends an existing CordRep instance to this tree.
|
||||
// The below methods accept three types of input:
|
||||
// 1) `rep` is a data node (See `IsDataNode` for valid data edges).
|
||||
// `rep` is appended or prepended to this tree 'as is'.
|
||||
// 2) `rep` is a BTREE.
|
||||
// `rep` is merged into `tree` respecting the Append/Prepend order.
|
||||
// 3) `rep` is some other (legacy) type.
|
||||
// `rep` is converted in place and added to `tree`
|
||||
// Requires `tree` and `rep` to be not null.
|
||||
static CordRepBtree* Append(CordRepBtree* tree, CordRep* rep);
|
||||
static CordRepBtree* Prepend(CordRepBtree* tree, CordRep* rep);
|
||||
|
||||
// Append/Prepend the data in `data` to this tree.
|
||||
// The `extra` parameter defines how much extra capacity should be allocated
|
||||
// for any additional FLAT being allocated. This is an optimization hint from
|
||||
// the caller. For example, a caller may need to add 2 string_views of data
|
||||
// "abc" and "defghi" which are not consecutive. The caller can in this case
|
||||
// invoke `AddData(tree, "abc", 6)`, and any newly added flat is allocated
|
||||
// where possible with at least 6 bytes of extra capacity beyond `length`.
|
||||
// This helps avoiding data getting fragmented over multiple flats.
|
||||
// There is no limit on the size of `data`. If `data` can not be stored inside
|
||||
// a single flat, then the function will iteratively add flats until all data
|
||||
// has been consumed and appended or prepended to the tree.
|
||||
static CordRepBtree* Append(CordRepBtree* tree, string_view data,
|
||||
size_t extra = 0);
|
||||
static CordRepBtree* Prepend(CordRepBtree* tree, string_view data,
|
||||
size_t extra = 0);
|
||||
|
||||
// Returns a new tree, containing `n` bytes of data from this instance
|
||||
// starting at offset `offset`. Where possible, the returned tree shares
|
||||
// (re-uses) data edges and nodes with this instance to minimize the
|
||||
// combined memory footprint of both trees.
|
||||
// Requires `offset + n <= length`. Returns `nullptr` if `n` is zero.
|
||||
CordRep* SubTree(size_t offset, size_t n);
|
||||
|
||||
// Removes `n` trailing bytes from `tree`, and returns the resulting tree
|
||||
// or data edge. Returns `tree` if n is zero, and nullptr if n == length.
|
||||
// This function is logically identical to:
|
||||
// result = tree->SubTree(0, tree->length - n);
|
||||
// Unref(tree);
|
||||
// return result;
|
||||
// However, the actual implementation will as much as possible perform 'in
|
||||
// place' modifications on the tree on all nodes and edges that are mutable.
|
||||
// For example, in a fully privately owned tree with the last edge being a
|
||||
// flat of length 12, RemoveSuffix(1) will simply set the length of that data
|
||||
// edge to 11, and reduce the length of all nodes on the edge path by 1.
|
||||
static CordRep* RemoveSuffix(CordRepBtree* tree, size_t n);
|
||||
|
||||
// Returns the character at the given offset.
|
||||
char GetCharacter(size_t offset) const;
|
||||
|
||||
// Returns true if this node holds a single data edge, and if so, sets
|
||||
// `fragment` to reference the contained data. `fragment` is an optional
|
||||
// output parameter and allowed to be null.
|
||||
bool IsFlat(absl::string_view* fragment) const;
|
||||
|
||||
// Returns true if the data of `n` bytes starting at offset `offset`
|
||||
// is contained in a single data edge, and if so, sets fragment to reference
|
||||
// the contained data. `fragment` is an optional output parameter and allowed
|
||||
// to be null.
|
||||
bool IsFlat(size_t offset, size_t n, absl::string_view* fragment) const;
|
||||
|
||||
// Returns a span (mutable range of bytes) of up to `size` bytes into the
|
||||
// last FLAT data edge inside this tree under the following conditions:
|
||||
// - none of the nodes down into the FLAT node are shared.
|
||||
// - the last data edge in this tree is a non-shared FLAT.
|
||||
// - the referenced FLAT has additional capacity available.
|
||||
// If all these conditions are met, a non-empty span is returned, and the
|
||||
// length of the flat node and involved tree nodes have been increased by
|
||||
// `span.length()`. The caller is responsible for immediately assigning values
|
||||
// to all uninitialized data reference by the returned span.
|
||||
// Requires `this->refcount.IsOne()`: this function forces the caller to do
|
||||
// this fast path check on the top level node, as this is the most commonly
|
||||
// shared node of a cord tree.
|
||||
Span<char> GetAppendBuffer(size_t size);
|
||||
|
||||
// Extracts the right-most data edge from this tree iff:
|
||||
// - the tree and all internal edges to the right-most node are not shared.
|
||||
// - the right-most node is a FLAT node and not shared.
|
||||
// - the right-most node has at least the desired extra capacity.
|
||||
//
|
||||
// Returns {tree, nullptr} if any of the above conditions are not met.
|
||||
// This method effectively removes data from the tree. The intent of this
|
||||
// method is to allow applications appending small string data to use
|
||||
// pre-existing capacity, and add the modified rep back to the tree.
|
||||
//
|
||||
// Simplified such code would look similar to this:
|
||||
// void MyTreeBuilder::Append(string_view data) {
|
||||
// ExtractResult result = CordRepBtree::ExtractAppendBuffer(tree_, 1);
|
||||
// if (CordRep* rep = result.extracted) {
|
||||
// size_t available = rep->Capacity() - rep->length;
|
||||
// size_t n = std::min(data.size(), n);
|
||||
// memcpy(rep->Data(), data.data(), n);
|
||||
// rep->length += n;
|
||||
// data.remove_prefix(n);
|
||||
// if (!result.tree->IsBtree()) {
|
||||
// tree_ = CordRepBtree::Create(result.tree);
|
||||
// }
|
||||
// tree_ = CordRepBtree::Append(tree_, rep);
|
||||
// }
|
||||
// ...
|
||||
// // Remaining edge in `result.tree`.
|
||||
// }
|
||||
static ExtractResult ExtractAppendBuffer(CordRepBtree* tree,
|
||||
size_t extra_capacity = 1);
|
||||
|
||||
// Returns the `height` of the tree. The height of a tree is limited to
|
||||
// kMaxHeight. `height` is implemented as an `int` as in some places we
|
||||
// use negative (-1) values for 'data edges'.
|
||||
int height() const { return static_cast<int>(storage[0]); }
|
||||
|
||||
// Properties: begin, back, end, front/back boundary indexes.
|
||||
size_t begin() const { return static_cast<size_t>(storage[1]); }
|
||||
size_t back() const { return static_cast<size_t>(storage[2]) - 1; }
|
||||
size_t end() const { return static_cast<size_t>(storage[2]); }
|
||||
size_t index(EdgeType edge) const {
|
||||
return edge == kFront ? begin() : back();
|
||||
}
|
||||
|
||||
// Properties: size and capacity.
|
||||
// `capacity` contains the current capacity of this instance, where
|
||||
// `kMaxCapacity` contains the maximum capacity of a btree node.
|
||||
// For now, `capacity` and `kMaxCapacity` return the same value, but this may
|
||||
// change in the future if we see benefit in dynamically sizing 'small' nodes
|
||||
// to 'large' nodes for large data trees.
|
||||
size_t size() const { return end() - begin(); }
|
||||
size_t capacity() const { return kMaxCapacity; }
|
||||
|
||||
// Edge access
|
||||
inline CordRep* Edge(size_t index) const;
|
||||
inline CordRep* Edge(EdgeType edge_type) const;
|
||||
inline absl::Span<CordRep* const> Edges() const;
|
||||
inline absl::Span<CordRep* const> Edges(size_t begin, size_t end) const;
|
||||
|
||||
// Returns reference to the data edge at `index`.
|
||||
// Requires this instance to be a leaf node, and `index` to be valid index.
|
||||
inline absl::string_view Data(size_t index) const;
|
||||
|
||||
// Diagnostics: returns true if `tree` is valid and internally consistent.
|
||||
// If `shallow` is false, then the provided top level node and all child nodes
|
||||
// below it are recursively checked. If `shallow` is true, only the provided
|
||||
// node in `tree` and the cumulative length, type and height of the direct
|
||||
// child nodes of `tree` are checked. The value of `shallow` is ignored if the
|
||||
// internal `cord_btree_exhaustive_validation` diagnostics variable is true,
|
||||
// in which case the performed validations works as if `shallow` were false.
|
||||
// This function is intended for debugging and testing purposes only.
|
||||
static bool IsValid(const CordRepBtree* tree, bool shallow = false);
|
||||
|
||||
// Diagnostics: asserts that the provided tree is valid.
|
||||
// `AssertValid()` performs a shallow validation by default. `shallow` can be
|
||||
// set to false in which case an exhaustive validation is performed. This
|
||||
// function is implemented in terms of calling `IsValid()` and asserting the
|
||||
// return value to be true. See `IsValid()` for more information.
|
||||
// This function is intended for debugging and testing purposes only.
|
||||
static CordRepBtree* AssertValid(CordRepBtree* tree, bool shallow = true);
|
||||
static const CordRepBtree* AssertValid(const CordRepBtree* tree,
|
||||
bool shallow = true);
|
||||
|
||||
// Diagnostics: dump the contents of this tree to `stream`.
|
||||
// This function is intended for debugging and testing purposes only.
|
||||
static void Dump(const CordRep* rep, std::ostream& stream);
|
||||
static void Dump(const CordRep* rep, absl::string_view label,
|
||||
std::ostream& stream);
|
||||
static void Dump(const CordRep* rep, absl::string_view label,
|
||||
bool include_contents, std::ostream& stream);
|
||||
|
||||
// Adds the edge `edge` to this node if possible. `owned` indicates if the
|
||||
// current node is potentially shared or not with other threads. Returns:
|
||||
// - {kSelf, <this>}
|
||||
// The edge was directly added to this node.
|
||||
// - {kCopied, <node>}
|
||||
// The edge was added to a copy of this node.
|
||||
// - {kPopped, New(edge, height())}
|
||||
// A new leg with the edge was created as this node has no extra capacity.
|
||||
template <EdgeType edge_type>
|
||||
inline OpResult AddEdge(bool owned, CordRep* edge, size_t delta);
|
||||
|
||||
// Replaces the front or back edge with the provided new edge. Returns:
|
||||
// - {kSelf, <this>}
|
||||
// The edge was directly set in this node. The old edge is unreffed.
|
||||
// - {kCopied, <node>}
|
||||
// A copy of this node was created with the new edge value.
|
||||
// In both cases, the function adopts a reference on `edge`.
|
||||
template <EdgeType edge_type>
|
||||
OpResult SetEdge(bool owned, CordRep* edge, size_t delta);
|
||||
|
||||
// Creates a new empty node at the specified height.
|
||||
static CordRepBtree* New(int height = 0);
|
||||
|
||||
// Creates a new node containing `rep`, with the height being computed
|
||||
// automatically based on the type of `rep`.
|
||||
static CordRepBtree* New(CordRep* rep);
|
||||
|
||||
// Creates a new node containing both `front` and `back` at height
|
||||
// `front.height() + 1`. Requires `back.height() == front.height()`.
|
||||
static CordRepBtree* New(CordRepBtree* front, CordRepBtree* back);
|
||||
|
||||
// Creates a fully balanced tree from the provided tree by rebuilding a new
|
||||
// tree from all data edges in the input. This function is automatically
|
||||
// invoked internally when the tree exceeds the maximum height.
|
||||
static CordRepBtree* Rebuild(CordRepBtree* tree);
|
||||
|
||||
private:
|
||||
CordRepBtree() = default;
|
||||
~CordRepBtree() = default;
|
||||
|
||||
// Initializes the main properties `tag`, `begin`, `end`, `height`.
|
||||
inline void InitInstance(int height, size_t begin = 0, size_t end = 0);
|
||||
|
||||
// Direct property access begin / end
|
||||
void set_begin(size_t begin) { storage[1] = static_cast<uint8_t>(begin); }
|
||||
void set_end(size_t end) { storage[2] = static_cast<uint8_t>(end); }
|
||||
|
||||
// Decreases the value of `begin` by `n`, and returns the new value. Notice
|
||||
// how this returns the new value unlike atomic::fetch_add which returns the
|
||||
// old value. This is because this is used to prepend edges at 'begin - 1'.
|
||||
size_t sub_fetch_begin(size_t n) {
|
||||
storage[1] -= static_cast<uint8_t>(n);
|
||||
return storage[1];
|
||||
}
|
||||
|
||||
// Increases the value of `end` by `n`, and returns the previous value. This
|
||||
// function is typically used to append edges at 'end'.
|
||||
size_t fetch_add_end(size_t n) {
|
||||
const uint8_t current = storage[2];
|
||||
storage[2] = static_cast<uint8_t>(current + n);
|
||||
return current;
|
||||
}
|
||||
|
||||
// Returns the index of the last edge starting on, or before `offset`, with
|
||||
// `n` containing the relative offset of `offset` inside that edge.
|
||||
// Requires `offset` < length.
|
||||
Position IndexOf(size_t offset) const;
|
||||
|
||||
// Returns the index of the last edge starting before `offset`, with `n`
|
||||
// containing the relative offset of `offset` inside that edge.
|
||||
// This function is useful to find the edges for some span of bytes ending at
|
||||
// `offset` (i.e., `n` bytes). For example:
|
||||
//
|
||||
// Position pos = IndexBefore(n)
|
||||
// edges = Edges(begin(), pos.index) // All full edges (may be empty)
|
||||
// last = Sub(Edge(pos.index), 0, pos.n) // Last partial edge (may be empty)
|
||||
//
|
||||
// Requires 0 < `offset` <= length.
|
||||
Position IndexBefore(size_t offset) const;
|
||||
|
||||
// Returns the index of the edge ending at (or on) length `length`, and the
|
||||
// number of bytes inside that edge up to `length`. For example, if we have a
|
||||
// Node with 2 edges, one of 10 and one of 20 long, then IndexOfLength(27)
|
||||
// will return {1, 17}, and IndexOfLength(10) will return {0, 10}.
|
||||
Position IndexOfLength(size_t n) const;
|
||||
|
||||
// Identical to the above function except starting from the position `front`.
|
||||
// This function is equivalent to `IndexBefore(front.n + offset)`, with
|
||||
// the difference that this function is optimized to start at `front.index`.
|
||||
Position IndexBefore(Position front, size_t offset) const;
|
||||
|
||||
// Returns the index of the edge directly beyond the edge containing offset
|
||||
// `offset`, with `n` containing the distance of that edge from `offset`.
|
||||
// This function is useful for iteratively finding suffix nodes and remaining
|
||||
// partial bytes in left-most suffix nodes as for example in CopySuffix.
|
||||
// Requires `offset` < length.
|
||||
Position IndexBeyond(size_t offset) const;
|
||||
|
||||
// Creates a new leaf node containing as much data as possible from `data`.
|
||||
// The data is added either forwards or reversed depending on `edge_type`.
|
||||
// Callers must check the length of the returned node to determine if all data
|
||||
// was copied or not.
|
||||
// See the `Append/Prepend` function for the meaning and purpose of `extra`.
|
||||
template <EdgeType edge_type>
|
||||
static CordRepBtree* NewLeaf(absl::string_view data, size_t extra);
|
||||
|
||||
// Creates a raw copy of this Btree node with the specified length, copying
|
||||
// all properties, but without adding any references to existing edges.
|
||||
CordRepBtree* CopyRaw(size_t new_length) const;
|
||||
|
||||
// Creates a full copy of this Btree node, adding a reference on all edges.
|
||||
CordRepBtree* Copy() const;
|
||||
|
||||
// Creates a partial copy of this Btree node, copying all edges up to `end`,
|
||||
// adding a reference on each copied edge, and sets the length of the newly
|
||||
// created copy to `new_length`.
|
||||
CordRepBtree* CopyBeginTo(size_t end, size_t new_length) const;
|
||||
|
||||
// Returns a tree containing the edges [tree->begin(), end) and length
|
||||
// of `new_length`. This method consumes a reference on the provided
|
||||
// tree, and logically performs the following operation:
|
||||
// result = tree->CopyBeginTo(end, new_length);
|
||||
// CordRep::Unref(tree);
|
||||
// return result;
|
||||
static CordRepBtree* ConsumeBeginTo(CordRepBtree* tree, size_t end,
|
||||
size_t new_length);
|
||||
|
||||
// Creates a partial copy of this Btree node, copying all edges starting at
|
||||
// `begin`, adding a reference on each copied edge, and sets the length of
|
||||
// the newly created copy to `new_length`.
|
||||
CordRepBtree* CopyToEndFrom(size_t begin, size_t new_length) const;
|
||||
|
||||
// Extracts and returns the front edge from the provided tree.
|
||||
// This method consumes a reference on the provided tree, and logically
|
||||
// performs the following operation:
|
||||
// edge = CordRep::Ref(tree->Edge(kFront));
|
||||
// CordRep::Unref(tree);
|
||||
// return edge;
|
||||
static CordRep* ExtractFront(CordRepBtree* tree);
|
||||
|
||||
// Returns a tree containing the result of appending `right` to `left`.
|
||||
static CordRepBtree* MergeTrees(CordRepBtree* left, CordRepBtree* right);
|
||||
|
||||
// Fallback functions for `Create()`, `Append()` and `Prepend()` which
|
||||
// deal with legacy / non conforming input, i.e.: CONCAT trees.
|
||||
static CordRepBtree* CreateSlow(CordRep* rep);
|
||||
static CordRepBtree* AppendSlow(CordRepBtree*, CordRep* rep);
|
||||
static CordRepBtree* PrependSlow(CordRepBtree*, CordRep* rep);
|
||||
|
||||
// Recursively rebuilds `tree` into `stack`. If 'consume` is set to true, the
|
||||
// function will consume a reference on `tree`. `stack` is a null terminated
|
||||
// array containing the new tree's state, with the current leaf node at
|
||||
// stack[0], and parent nodes above that, or null for 'top of tree'.
|
||||
static void Rebuild(CordRepBtree** stack, CordRepBtree* tree, bool consume);
|
||||
|
||||
// Aligns existing edges to start at index 0, to allow for a new edge to be
|
||||
// added to the back of the current edges.
|
||||
inline void AlignBegin();
|
||||
|
||||
// Aligns existing edges to end at `capacity`, to allow for a new edge to be
|
||||
// added in front of the current edges.
|
||||
inline void AlignEnd();
|
||||
|
||||
// Adds the provided edge to this node.
|
||||
// Requires this node to have capacity for the edge. Realigns / moves
|
||||
// existing edges as needed to prepend or append the new edge.
|
||||
template <EdgeType edge_type>
|
||||
inline void Add(CordRep* rep);
|
||||
|
||||
// Adds the provided edges to this node.
|
||||
// Requires this node to have capacity for the edges. Realigns / moves
|
||||
// existing edges as needed to prepend or append the new edges.
|
||||
template <EdgeType edge_type>
|
||||
inline void Add(absl::Span<CordRep* const>);
|
||||
|
||||
// Adds data from `data` to this node until either all data has been consumed,
|
||||
// or there is no more capacity for additional flat nodes inside this node.
|
||||
// Requires the current node to be a leaf node, data to be non empty, and the
|
||||
// current node to have capacity for at least one more data edge.
|
||||
// Returns any remaining data from `data` that was not added, which is
|
||||
// depending on the edge type (front / back) either the remaining prefix of
|
||||
// suffix of the input.
|
||||
// See the `Append/Prepend` function for the meaning and purpose of `extra`.
|
||||
template <EdgeType edge_type>
|
||||
absl::string_view AddData(absl::string_view data, size_t extra);
|
||||
|
||||
// Replace the front or back edge with the provided value.
|
||||
// Adopts a reference on `edge` and unrefs the old edge.
|
||||
template <EdgeType edge_type>
|
||||
inline void SetEdge(CordRep* edge);
|
||||
|
||||
// Returns a partial copy of the current tree containing the first `n` bytes
|
||||
// of data. `CopyResult` contains both the resulting edge and its height. The
|
||||
// resulting tree may be less high than the current tree, or even be a single
|
||||
// matching data edge if `allow_folding` is set to true.
|
||||
// For example, if `n == 1`, then the result will be the single data edge, and
|
||||
// height will be set to -1 (one below the owning leaf node). If n == 0, this
|
||||
// function returns null. Requires `n <= length`
|
||||
CopyResult CopyPrefix(size_t n, bool allow_folding = true);
|
||||
|
||||
// Returns a partial copy of the current tree containing all data starting
|
||||
// after `offset`. `CopyResult` contains both the resulting edge and its
|
||||
// height. The resulting tree may be less high than the current tree, or even
|
||||
// be a single matching data edge. For example, if `n == length - 1`, then the
|
||||
// result will be a single data edge, and height will be set to -1 (one below
|
||||
// the owning leaf node).
|
||||
// Requires `offset < length`
|
||||
CopyResult CopySuffix(size_t offset);
|
||||
|
||||
// Returns a OpResult value of {this, kSelf} or {Copy(), kCopied}
|
||||
// depending on the value of `owned`.
|
||||
inline OpResult ToOpResult(bool owned);
|
||||
|
||||
// Adds `rep` to the specified tree, returning the modified tree.
|
||||
template <EdgeType edge_type>
|
||||
static CordRepBtree* AddCordRep(CordRepBtree* tree, CordRep* rep);
|
||||
|
||||
// Adds `data` to the specified tree, returning the modified tree.
|
||||
// See the `Append/Prepend` function for the meaning and purpose of `extra`.
|
||||
template <EdgeType edge_type>
|
||||
static CordRepBtree* AddData(CordRepBtree* tree, absl::string_view data,
|
||||
size_t extra = 0);
|
||||
|
||||
// Merges `src` into `dst` with `src` being added either before (kFront) or
|
||||
// after (kBack) `dst`. Requires the height of `dst` to be greater than or
|
||||
// equal to the height of `src`.
|
||||
template <EdgeType edge_type>
|
||||
static CordRepBtree* Merge(CordRepBtree* dst, CordRepBtree* src);
|
||||
|
||||
// Fallback version of GetAppendBuffer for large trees: GetAppendBuffer()
|
||||
// implements an inlined version for trees of limited height (3 levels),
|
||||
// GetAppendBufferSlow implements the logic for large trees.
|
||||
Span<char> GetAppendBufferSlow(size_t size);
|
||||
|
||||
// `edges_` contains all edges starting from this instance.
|
||||
// These are explicitly `child` edges only, a cord btree (or any cord tree in
|
||||
// that respect) does not store `parent` pointers anywhere: multiple trees /
|
||||
// parents can reference the same shared child edge. The type of these edges
|
||||
// depends on the height of the node. `Leaf nodes` (height == 0) contain `data
|
||||
// edges` (external or flat nodes, or sub-strings thereof). All other nodes
|
||||
// (height > 0) contain pointers to BTREE nodes with a height of `height - 1`.
|
||||
CordRep* edges_[kMaxCapacity];
|
||||
|
||||
friend class CordRepBtreeTestPeer;
|
||||
friend class CordRepBtreeNavigator;
|
||||
};
|
||||
|
||||
inline CordRepBtree* CordRep::btree() {
|
||||
assert(IsBtree());
|
||||
return static_cast<CordRepBtree*>(this);
|
||||
}
|
||||
|
||||
inline const CordRepBtree* CordRep::btree() const {
|
||||
assert(IsBtree());
|
||||
return static_cast<const CordRepBtree*>(this);
|
||||
}
|
||||
|
||||
inline void CordRepBtree::InitInstance(int height, size_t begin, size_t end) {
|
||||
tag = BTREE;
|
||||
storage[0] = static_cast<uint8_t>(height);
|
||||
storage[1] = static_cast<uint8_t>(begin);
|
||||
storage[2] = static_cast<uint8_t>(end);
|
||||
}
|
||||
|
||||
inline CordRep* CordRepBtree::Edge(size_t index) const {
|
||||
assert(index >= begin());
|
||||
assert(index < end());
|
||||
return edges_[index];
|
||||
}
|
||||
|
||||
inline CordRep* CordRepBtree::Edge(EdgeType edge_type) const {
|
||||
return edges_[edge_type == kFront ? begin() : back()];
|
||||
}
|
||||
|
||||
inline absl::Span<CordRep* const> CordRepBtree::Edges() const {
|
||||
return {edges_ + begin(), size()};
|
||||
}
|
||||
|
||||
inline absl::Span<CordRep* const> CordRepBtree::Edges(size_t begin,
|
||||
size_t end) const {
|
||||
assert(begin <= end);
|
||||
assert(begin >= this->begin());
|
||||
assert(end <= this->end());
|
||||
return {edges_ + begin, static_cast<size_t>(end - begin)};
|
||||
}
|
||||
|
||||
inline absl::string_view CordRepBtree::Data(size_t index) const {
|
||||
assert(height() == 0);
|
||||
return EdgeData(Edge(index));
|
||||
}
|
||||
|
||||
inline CordRepBtree* CordRepBtree::New(int height) {
|
||||
CordRepBtree* tree = new CordRepBtree;
|
||||
tree->length = 0;
|
||||
tree->InitInstance(height);
|
||||
return tree;
|
||||
}
|
||||
|
||||
inline CordRepBtree* CordRepBtree::New(CordRep* rep) {
|
||||
CordRepBtree* tree = new CordRepBtree;
|
||||
int height = rep->IsBtree() ? rep->btree()->height() + 1 : 0;
|
||||
tree->length = rep->length;
|
||||
tree->InitInstance(height, /*begin=*/0, /*end=*/1);
|
||||
tree->edges_[0] = rep;
|
||||
return tree;
|
||||
}
|
||||
|
||||
inline CordRepBtree* CordRepBtree::New(CordRepBtree* front,
|
||||
CordRepBtree* back) {
|
||||
assert(front->height() == back->height());
|
||||
CordRepBtree* tree = new CordRepBtree;
|
||||
tree->length = front->length + back->length;
|
||||
tree->InitInstance(front->height() + 1, /*begin=*/0, /*end=*/2);
|
||||
tree->edges_[0] = front;
|
||||
tree->edges_[1] = back;
|
||||
return tree;
|
||||
}
|
||||
|
||||
inline void CordRepBtree::Unref(absl::Span<CordRep* const> edges) {
|
||||
for (CordRep* edge : edges) {
|
||||
if (ABSL_PREDICT_FALSE(!edge->refcount.Decrement())) {
|
||||
CordRep::Destroy(edge);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline CordRepBtree* CordRepBtree::CopyRaw(size_t new_length) const {
|
||||
CordRepBtree* tree = new CordRepBtree;
|
||||
|
||||
// `length` and `refcount` are the first members of `CordRepBtree`.
|
||||
// We initialize `length` using the given length, have `refcount` be set to
|
||||
// ref = 1 through its default constructor, and copy all data beyond
|
||||
// 'refcount' which starts with `tag` using a single memcpy: all contents
|
||||
// except `refcount` is trivially copyable, and the compiler does not
|
||||
// efficiently coalesce member-wise copy of these members.
|
||||
// See https://gcc.godbolt.org/z/qY8zsca6z
|
||||
// # LINT.IfChange(copy_raw)
|
||||
tree->length = new_length;
|
||||
uint8_t* dst = &tree->tag;
|
||||
const uint8_t* src = &tag;
|
||||
const ptrdiff_t offset = src - reinterpret_cast<const uint8_t*>(this);
|
||||
memcpy(dst, src, sizeof(CordRepBtree) - static_cast<size_t>(offset));
|
||||
return tree;
|
||||
// # LINT.ThenChange()
|
||||
}
|
||||
|
||||
inline CordRepBtree* CordRepBtree::Copy() const {
|
||||
CordRepBtree* tree = CopyRaw(length);
|
||||
for (CordRep* rep : Edges()) CordRep::Ref(rep);
|
||||
return tree;
|
||||
}
|
||||
|
||||
inline CordRepBtree* CordRepBtree::CopyToEndFrom(size_t begin,
|
||||
size_t new_length) const {
|
||||
assert(begin >= this->begin());
|
||||
assert(begin <= this->end());
|
||||
CordRepBtree* tree = CopyRaw(new_length);
|
||||
tree->set_begin(begin);
|
||||
for (CordRep* edge : tree->Edges()) CordRep::Ref(edge);
|
||||
return tree;
|
||||
}
|
||||
|
||||
inline CordRepBtree* CordRepBtree::CopyBeginTo(size_t end,
|
||||
size_t new_length) const {
|
||||
assert(end <= capacity());
|
||||
assert(end >= this->begin());
|
||||
CordRepBtree* tree = CopyRaw(new_length);
|
||||
tree->set_end(end);
|
||||
for (CordRep* edge : tree->Edges()) CordRep::Ref(edge);
|
||||
return tree;
|
||||
}
|
||||
|
||||
inline void CordRepBtree::AlignBegin() {
|
||||
// The below code itself does not need to be fast as typically we have
|
||||
// mono-directional append/prepend calls, and `begin` / `end` are typically
|
||||
// adjusted no more than once. But we want to avoid potential register clobber
|
||||
// effects, making the compiler emit register save/store/spills, and minimize
|
||||
// the size of code.
|
||||
const size_t delta = begin();
|
||||
if (ABSL_PREDICT_FALSE(delta != 0)) {
|
||||
const size_t new_end = end() - delta;
|
||||
set_begin(0);
|
||||
set_end(new_end);
|
||||
// TODO(mvels): we can write this using 2 loads / 2 stores depending on
|
||||
// total size for the kMaxCapacity = 6 case. I.e., we can branch (switch) on
|
||||
// size, and then do overlapping load/store of up to 4 pointers (inlined as
|
||||
// XMM, YMM or ZMM load/store) and up to 2 pointers (XMM / YMM), which is a)
|
||||
// compact and b) not clobbering any registers.
|
||||
ABSL_ASSUME(new_end <= kMaxCapacity);
|
||||
#ifdef __clang__
|
||||
#pragma unroll 1
|
||||
#endif
|
||||
for (size_t i = 0; i < new_end; ++i) {
|
||||
edges_[i] = edges_[i + delta];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline void CordRepBtree::AlignEnd() {
|
||||
// See comments in `AlignBegin` for motivation on the hand-rolled for loops.
|
||||
const size_t delta = capacity() - end();
|
||||
if (delta != 0) {
|
||||
const size_t new_begin = begin() + delta;
|
||||
const size_t new_end = end() + delta;
|
||||
set_begin(new_begin);
|
||||
set_end(new_end);
|
||||
ABSL_ASSUME(new_end <= kMaxCapacity);
|
||||
#ifdef __clang__
|
||||
#pragma unroll 1
|
||||
#endif
|
||||
for (size_t i = new_end - 1; i >= new_begin; --i) {
|
||||
edges_[i] = edges_[i - delta];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
inline void CordRepBtree::Add<CordRepBtree::kBack>(CordRep* rep) {
|
||||
AlignBegin();
|
||||
edges_[fetch_add_end(1)] = rep;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline void CordRepBtree::Add<CordRepBtree::kBack>(
|
||||
absl::Span<CordRep* const> edges) {
|
||||
AlignBegin();
|
||||
size_t new_end = end();
|
||||
for (CordRep* edge : edges) edges_[new_end++] = edge;
|
||||
set_end(new_end);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline void CordRepBtree::Add<CordRepBtree::kFront>(CordRep* rep) {
|
||||
AlignEnd();
|
||||
edges_[sub_fetch_begin(1)] = rep;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline void CordRepBtree::Add<CordRepBtree::kFront>(
|
||||
absl::Span<CordRep* const> edges) {
|
||||
AlignEnd();
|
||||
size_t new_begin = begin() - edges.size();
|
||||
set_begin(new_begin);
|
||||
for (CordRep* edge : edges) edges_[new_begin++] = edge;
|
||||
}
|
||||
|
||||
template <CordRepBtree::EdgeType edge_type>
|
||||
inline void CordRepBtree::SetEdge(CordRep* edge) {
|
||||
const int idx = edge_type == kFront ? begin() : back();
|
||||
CordRep::Unref(edges_[idx]);
|
||||
edges_[idx] = edge;
|
||||
}
|
||||
|
||||
inline CordRepBtree::OpResult CordRepBtree::ToOpResult(bool owned) {
|
||||
return owned ? OpResult{this, kSelf} : OpResult{Copy(), kCopied};
|
||||
}
|
||||
|
||||
inline CordRepBtree::Position CordRepBtree::IndexOf(size_t offset) const {
|
||||
assert(offset < length);
|
||||
size_t index = begin();
|
||||
while (offset >= edges_[index]->length) offset -= edges_[index++]->length;
|
||||
return {index, offset};
|
||||
}
|
||||
|
||||
inline CordRepBtree::Position CordRepBtree::IndexBefore(size_t offset) const {
|
||||
assert(offset > 0);
|
||||
assert(offset <= length);
|
||||
size_t index = begin();
|
||||
while (offset > edges_[index]->length) offset -= edges_[index++]->length;
|
||||
return {index, offset};
|
||||
}
|
||||
|
||||
inline CordRepBtree::Position CordRepBtree::IndexBefore(Position front,
|
||||
size_t offset) const {
|
||||
size_t index = front.index;
|
||||
offset = offset + front.n;
|
||||
while (offset > edges_[index]->length) offset -= edges_[index++]->length;
|
||||
return {index, offset};
|
||||
}
|
||||
|
||||
inline CordRepBtree::Position CordRepBtree::IndexOfLength(size_t n) const {
|
||||
assert(n <= length);
|
||||
size_t index = back();
|
||||
size_t strip = length - n;
|
||||
while (strip >= edges_[index]->length) strip -= edges_[index--]->length;
|
||||
return {index, edges_[index]->length - strip};
|
||||
}
|
||||
|
||||
inline CordRepBtree::Position CordRepBtree::IndexBeyond(
|
||||
const size_t offset) const {
|
||||
// We need to find the edge which `starting offset` is beyond (>=)`offset`.
|
||||
// For this we can't use the `offset -= length` logic of IndexOf. Instead, we
|
||||
// track the offset of the `current edge` in `off`, which we increase as we
|
||||
// iterate over the edges until we find the matching edge.
|
||||
size_t off = 0;
|
||||
size_t index = begin();
|
||||
while (offset > off) off += edges_[index++]->length;
|
||||
return {index, off - offset};
|
||||
}
|
||||
|
||||
inline CordRepBtree* CordRepBtree::Create(CordRep* rep) {
|
||||
if (IsDataEdge(rep)) return New(rep);
|
||||
return CreateSlow(rep);
|
||||
}
|
||||
|
||||
inline Span<char> CordRepBtree::GetAppendBuffer(size_t size) {
|
||||
assert(refcount.IsOne());
|
||||
CordRepBtree* tree = this;
|
||||
const int height = this->height();
|
||||
CordRepBtree* n1 = tree;
|
||||
CordRepBtree* n2 = tree;
|
||||
CordRepBtree* n3 = tree;
|
||||
switch (height) {
|
||||
case 3:
|
||||
tree = tree->Edge(kBack)->btree();
|
||||
if (!tree->refcount.IsOne()) return {};
|
||||
n2 = tree;
|
||||
ABSL_FALLTHROUGH_INTENDED;
|
||||
case 2:
|
||||
tree = tree->Edge(kBack)->btree();
|
||||
if (!tree->refcount.IsOne()) return {};
|
||||
n1 = tree;
|
||||
ABSL_FALLTHROUGH_INTENDED;
|
||||
case 1:
|
||||
tree = tree->Edge(kBack)->btree();
|
||||
if (!tree->refcount.IsOne()) return {};
|
||||
ABSL_FALLTHROUGH_INTENDED;
|
||||
case 0:
|
||||
CordRep* edge = tree->Edge(kBack);
|
||||
if (!edge->refcount.IsOne()) return {};
|
||||
if (edge->tag < FLAT) return {};
|
||||
size_t avail = edge->flat()->Capacity() - edge->length;
|
||||
if (avail == 0) return {};
|
||||
size_t delta = (std::min)(size, avail);
|
||||
Span<char> span = {edge->flat()->Data() + edge->length, delta};
|
||||
edge->length += delta;
|
||||
switch (height) {
|
||||
case 3:
|
||||
n3->length += delta;
|
||||
ABSL_FALLTHROUGH_INTENDED;
|
||||
case 2:
|
||||
n2->length += delta;
|
||||
ABSL_FALLTHROUGH_INTENDED;
|
||||
case 1:
|
||||
n1->length += delta;
|
||||
ABSL_FALLTHROUGH_INTENDED;
|
||||
case 0:
|
||||
tree->length += delta;
|
||||
return span;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return GetAppendBufferSlow(size);
|
||||
}
|
||||
|
||||
extern template CordRepBtree* CordRepBtree::AddCordRep<CordRepBtree::kBack>(
|
||||
CordRepBtree* tree, CordRep* rep);
|
||||
|
||||
extern template CordRepBtree* CordRepBtree::AddCordRep<CordRepBtree::kFront>(
|
||||
CordRepBtree* tree, CordRep* rep);
|
||||
|
||||
inline CordRepBtree* CordRepBtree::Append(CordRepBtree* tree, CordRep* rep) {
|
||||
if (ABSL_PREDICT_TRUE(IsDataEdge(rep))) {
|
||||
return CordRepBtree::AddCordRep<kBack>(tree, rep);
|
||||
}
|
||||
return AppendSlow(tree, rep);
|
||||
}
|
||||
|
||||
inline CordRepBtree* CordRepBtree::Prepend(CordRepBtree* tree, CordRep* rep) {
|
||||
if (ABSL_PREDICT_TRUE(IsDataEdge(rep))) {
|
||||
return CordRepBtree::AddCordRep<kFront>(tree, rep);
|
||||
}
|
||||
return PrependSlow(tree, rep);
|
||||
}
|
||||
|
||||
#ifdef NDEBUG
|
||||
|
||||
inline CordRepBtree* CordRepBtree::AssertValid(CordRepBtree* tree,
|
||||
bool /* shallow */) {
|
||||
return tree;
|
||||
}
|
||||
|
||||
inline const CordRepBtree* CordRepBtree::AssertValid(const CordRepBtree* tree,
|
||||
bool /* shallow */) {
|
||||
return tree;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace cord_internal
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
|
||||
#endif // ABSL_STRINGS_INTERNAL_CORD_REP_BTREE_H_
|
||||
187
Pods/abseil/absl/strings/internal/cord_rep_btree_navigator.cc
generated
Normal file
187
Pods/abseil/absl/strings/internal/cord_rep_btree_navigator.cc
generated
Normal file
@@ -0,0 +1,187 @@
|
||||
// Copyright 2021 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/cord_rep_btree_navigator.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include "absl/strings/internal/cord_data_edge.h"
|
||||
#include "absl/strings/internal/cord_internal.h"
|
||||
#include "absl/strings/internal/cord_rep_btree.h"
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace cord_internal {
|
||||
|
||||
using ReadResult = CordRepBtreeNavigator::ReadResult;
|
||||
|
||||
namespace {
|
||||
|
||||
// Returns a `CordRepSubstring` from `rep` starting at `offset` of size `n`.
|
||||
// If `rep` is already a `CordRepSubstring` instance, an adjusted instance is
|
||||
// created based on the old offset and new offset.
|
||||
// Adopts a reference on `rep`. Rep must be a valid data edge. Returns
|
||||
// nullptr if `n == 0`, `rep` if `n == rep->length`.
|
||||
// Requires `offset < rep->length` and `offset + n <= rep->length`.
|
||||
// TODO(192061034): move to utility library in internal and optimize for small
|
||||
// substrings of larger reps.
|
||||
inline CordRep* Substring(CordRep* rep, size_t offset, size_t n) {
|
||||
assert(n <= rep->length);
|
||||
assert(offset < rep->length);
|
||||
assert(offset <= rep->length - n);
|
||||
assert(IsDataEdge(rep));
|
||||
|
||||
if (n == 0) return nullptr;
|
||||
if (n == rep->length) return CordRep::Ref(rep);
|
||||
|
||||
if (rep->tag == SUBSTRING) {
|
||||
offset += rep->substring()->start;
|
||||
rep = rep->substring()->child;
|
||||
}
|
||||
|
||||
assert(rep->IsExternal() || rep->IsFlat());
|
||||
CordRepSubstring* substring = new CordRepSubstring();
|
||||
substring->length = n;
|
||||
substring->tag = SUBSTRING;
|
||||
substring->start = offset;
|
||||
substring->child = CordRep::Ref(rep);
|
||||
return substring;
|
||||
}
|
||||
|
||||
inline CordRep* Substring(CordRep* rep, size_t offset) {
|
||||
return Substring(rep, offset, rep->length - offset);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
CordRepBtreeNavigator::Position CordRepBtreeNavigator::Skip(size_t n) {
|
||||
int height = 0;
|
||||
size_t index = index_[0];
|
||||
CordRepBtree* node = node_[0];
|
||||
CordRep* edge = node->Edge(index);
|
||||
|
||||
// Overall logic: Find an edge of at least the length we need to skip.
|
||||
// We consume all edges which are smaller (i.e., must be 100% skipped).
|
||||
// If we exhausted all edges on the current level, we move one level
|
||||
// up the tree, and repeat until we either find the edge, or until we hit
|
||||
// the top of the tree meaning the skip exceeds tree->length.
|
||||
while (n >= edge->length) {
|
||||
n -= edge->length;
|
||||
while (++index == node->end()) {
|
||||
if (++height > height_) return {nullptr, n};
|
||||
node = node_[height];
|
||||
index = index_[height];
|
||||
}
|
||||
edge = node->Edge(index);
|
||||
}
|
||||
|
||||
// If we moved up the tree, descend down to the leaf level, consuming all
|
||||
// edges that must be skipped.
|
||||
while (height > 0) {
|
||||
node = edge->btree();
|
||||
index_[height] = static_cast<uint8_t>(index);
|
||||
node_[--height] = node;
|
||||
index = node->begin();
|
||||
edge = node->Edge(index);
|
||||
while (n >= edge->length) {
|
||||
n -= edge->length;
|
||||
++index;
|
||||
assert(index != node->end());
|
||||
edge = node->Edge(index);
|
||||
}
|
||||
}
|
||||
index_[0] = static_cast<uint8_t>(index);
|
||||
return {edge, n};
|
||||
}
|
||||
|
||||
ReadResult CordRepBtreeNavigator::Read(size_t edge_offset, size_t n) {
|
||||
int height = 0;
|
||||
size_t length = edge_offset + n;
|
||||
size_t index = index_[0];
|
||||
CordRepBtree* node = node_[0];
|
||||
CordRep* edge = node->Edge(index);
|
||||
assert(edge_offset < edge->length);
|
||||
|
||||
if (length < edge->length) {
|
||||
return {Substring(edge, edge_offset, n), length};
|
||||
}
|
||||
|
||||
// Similar to 'Skip', we consume all edges that are inside the 'length' of
|
||||
// data that needs to be read. If we exhaust the current level, we move one
|
||||
// level up the tree and repeat until we hit the final edge that must be
|
||||
// (partially) read. We consume all edges into `subtree`.
|
||||
CordRepBtree* subtree = CordRepBtree::New(Substring(edge, edge_offset));
|
||||
size_t subtree_end = 1;
|
||||
do {
|
||||
length -= edge->length;
|
||||
while (++index == node->end()) {
|
||||
index_[height] = static_cast<uint8_t>(index);
|
||||
if (++height > height_) {
|
||||
subtree->set_end(subtree_end);
|
||||
if (length == 0) return {subtree, 0};
|
||||
CordRep::Unref(subtree);
|
||||
return {nullptr, length};
|
||||
}
|
||||
if (length != 0) {
|
||||
subtree->set_end(subtree_end);
|
||||
subtree = CordRepBtree::New(subtree);
|
||||
subtree_end = 1;
|
||||
}
|
||||
node = node_[height];
|
||||
index = index_[height];
|
||||
}
|
||||
edge = node->Edge(index);
|
||||
if (length >= edge->length) {
|
||||
subtree->length += edge->length;
|
||||
subtree->edges_[subtree_end++] = CordRep::Ref(edge);
|
||||
}
|
||||
} while (length >= edge->length);
|
||||
CordRepBtree* tree = subtree;
|
||||
subtree->length += length;
|
||||
|
||||
// If we moved up the tree, descend down to the leaf level, consuming all
|
||||
// edges that must be read, adding 'down' nodes to `subtree`.
|
||||
while (height > 0) {
|
||||
node = edge->btree();
|
||||
index_[height] = static_cast<uint8_t>(index);
|
||||
node_[--height] = node;
|
||||
index = node->begin();
|
||||
edge = node->Edge(index);
|
||||
|
||||
if (length != 0) {
|
||||
CordRepBtree* right = CordRepBtree::New(height);
|
||||
right->length = length;
|
||||
subtree->edges_[subtree_end++] = right;
|
||||
subtree->set_end(subtree_end);
|
||||
subtree = right;
|
||||
subtree_end = 0;
|
||||
while (length >= edge->length) {
|
||||
subtree->edges_[subtree_end++] = CordRep::Ref(edge);
|
||||
length -= edge->length;
|
||||
edge = node->Edge(++index);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Add any (partial) edge still remaining at the leaf level.
|
||||
if (length != 0) {
|
||||
subtree->edges_[subtree_end++] = Substring(edge, 0, length);
|
||||
}
|
||||
subtree->set_end(subtree_end);
|
||||
index_[0] = static_cast<uint8_t>(index);
|
||||
return {tree, length};
|
||||
}
|
||||
|
||||
} // namespace cord_internal
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
267
Pods/abseil/absl/strings/internal/cord_rep_btree_navigator.h
generated
Normal file
267
Pods/abseil/absl/strings/internal/cord_rep_btree_navigator.h
generated
Normal file
@@ -0,0 +1,267 @@
|
||||
// Copyright 2021 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_CORD_REP_BTREE_NAVIGATOR_H_
|
||||
#define ABSL_STRINGS_INTERNAL_CORD_REP_BTREE_NAVIGATOR_H_
|
||||
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
|
||||
#include "absl/strings/internal/cord_internal.h"
|
||||
#include "absl/strings/internal/cord_rep_btree.h"
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace cord_internal {
|
||||
|
||||
// CordRepBtreeNavigator is a bi-directional navigator allowing callers to
|
||||
// navigate all the (leaf) data edges in a CordRepBtree instance.
|
||||
//
|
||||
// A CordRepBtreeNavigator instance is by default empty. Callers initialize a
|
||||
// navigator instance by calling one of `InitFirst()`, `InitLast()` or
|
||||
// `InitOffset()`, which establishes a current position. Callers can then
|
||||
// navigate using the `Next`, `Previous`, `Skip` and `Seek` methods.
|
||||
//
|
||||
// The navigator instance does not take or adopt a reference on the provided
|
||||
// `tree` on any of the initialization calls. Callers are responsible for
|
||||
// guaranteeing the lifecycle of the provided tree. A navigator instance can
|
||||
// be reset to the empty state by calling `Reset`.
|
||||
//
|
||||
// A navigator only keeps positional state on the 'current data edge', it does
|
||||
// explicitly not keep any 'offset' state. The class does accept and return
|
||||
// offsets in the `Read()`, `Skip()` and 'Seek()` methods as these would
|
||||
// otherwise put a big burden on callers. Callers are expected to maintain
|
||||
// (returned) offset info if they require such granular state.
|
||||
class CordRepBtreeNavigator {
|
||||
public:
|
||||
// The logical position as returned by the Seek() and Skip() functions.
|
||||
// Returns the current leaf edge for the desired seek or skip position and
|
||||
// the offset of that position inside that edge.
|
||||
struct Position {
|
||||
CordRep* edge;
|
||||
size_t offset;
|
||||
};
|
||||
|
||||
// The read result as returned by the Read() function.
|
||||
// `tree` contains the resulting tree which is identical to the result
|
||||
// of calling CordRepBtree::SubTree(...) on the tree being navigated.
|
||||
// `n` contains the number of bytes used from the last navigated to
|
||||
// edge of the tree.
|
||||
struct ReadResult {
|
||||
CordRep* tree;
|
||||
size_t n;
|
||||
};
|
||||
|
||||
// Returns true if this instance is not empty.
|
||||
explicit operator bool() const;
|
||||
|
||||
// Returns the tree for this instance or nullptr if empty.
|
||||
CordRepBtree* btree() const;
|
||||
|
||||
// Returns the data edge of the current position.
|
||||
// Requires this instance to not be empty.
|
||||
CordRep* Current() const;
|
||||
|
||||
// Resets this navigator to `tree`, returning the first data edge in the tree.
|
||||
CordRep* InitFirst(CordRepBtree* tree);
|
||||
|
||||
// Resets this navigator to `tree`, returning the last data edge in the tree.
|
||||
CordRep* InitLast(CordRepBtree* tree);
|
||||
|
||||
// Resets this navigator to `tree` returning the data edge at position
|
||||
// `offset` and the relative offset of `offset` into that data edge.
|
||||
// Returns `Position.edge = nullptr` if the provided offset is greater
|
||||
// than or equal to the length of the tree, in which case the state of
|
||||
// the navigator instance remains unchanged.
|
||||
Position InitOffset(CordRepBtree* tree, size_t offset);
|
||||
|
||||
// Navigates to the next data edge.
|
||||
// Returns the next data edge or nullptr if there is no next data edge, in
|
||||
// which case the current position remains unchanged.
|
||||
CordRep* Next();
|
||||
|
||||
// Navigates to the previous data edge.
|
||||
// Returns the previous data edge or nullptr if there is no previous data
|
||||
// edge, in which case the current position remains unchanged.
|
||||
CordRep* Previous();
|
||||
|
||||
// Navigates to the data edge at position `offset`. Returns the navigated to
|
||||
// data edge in `Position.edge` and the relative offset of `offset` into that
|
||||
// data edge in `Position.offset`. Returns `Position.edge = nullptr` if the
|
||||
// provide offset is greater than or equal to the tree's length.
|
||||
Position Seek(size_t offset);
|
||||
|
||||
// Reads `n` bytes of data starting at offset `edge_offset` of the current
|
||||
// data edge, and returns the result in `ReadResult.tree`. `ReadResult.n`
|
||||
// contains the 'bytes used` from the last / current data edge in the tree.
|
||||
// This allows users that mix regular navigation (using string views) and
|
||||
// 'read into cord' navigation to keep track of the current state, and which
|
||||
// bytes have been consumed from a navigator.
|
||||
// This function returns `ReadResult.tree = nullptr` if the requested length
|
||||
// exceeds the length of the tree starting at the current data edge.
|
||||
ReadResult Read(size_t edge_offset, size_t n);
|
||||
|
||||
// Skips `n` bytes forward from the current data edge, returning the navigated
|
||||
// to data edge in `Position.edge` and `Position.offset` containing the offset
|
||||
// inside that data edge. Note that the state of the navigator is left
|
||||
// unchanged if `n` is smaller than the length of the current data edge.
|
||||
Position Skip(size_t n);
|
||||
|
||||
// Resets this instance to the default / empty state.
|
||||
void Reset();
|
||||
|
||||
private:
|
||||
// Slow path for Next() if Next() reached the end of a leaf node. Backtracks
|
||||
// up the stack until it finds a node that has a 'next' position available,
|
||||
// and then does a 'front dive' towards the next leaf node.
|
||||
CordRep* NextUp();
|
||||
|
||||
// Slow path for Previous() if Previous() reached the beginning of a leaf
|
||||
// node. Backtracks up the stack until it finds a node that has a 'previous'
|
||||
// position available, and then does a 'back dive' towards the previous leaf
|
||||
// node.
|
||||
CordRep* PreviousUp();
|
||||
|
||||
// Generic implementation of InitFirst() and InitLast().
|
||||
template <CordRepBtree::EdgeType edge_type>
|
||||
CordRep* Init(CordRepBtree* tree);
|
||||
|
||||
// `height_` contains the height of the current tree, or -1 if empty.
|
||||
int height_ = -1;
|
||||
|
||||
// `index_` and `node_` contain the navigation state as the 'path' to the
|
||||
// current data edge which is at `node_[0]->Edge(index_[0])`. The contents
|
||||
// of these are undefined until the instance is initialized (`height_ >= 0`).
|
||||
uint8_t index_[CordRepBtree::kMaxDepth];
|
||||
CordRepBtree* node_[CordRepBtree::kMaxDepth];
|
||||
};
|
||||
|
||||
// Returns true if this instance is not empty.
|
||||
inline CordRepBtreeNavigator::operator bool() const { return height_ >= 0; }
|
||||
|
||||
inline CordRepBtree* CordRepBtreeNavigator::btree() const {
|
||||
return height_ >= 0 ? node_[height_] : nullptr;
|
||||
}
|
||||
|
||||
inline CordRep* CordRepBtreeNavigator::Current() const {
|
||||
assert(height_ >= 0);
|
||||
return node_[0]->Edge(index_[0]);
|
||||
}
|
||||
|
||||
inline void CordRepBtreeNavigator::Reset() { height_ = -1; }
|
||||
|
||||
inline CordRep* CordRepBtreeNavigator::InitFirst(CordRepBtree* tree) {
|
||||
return Init<CordRepBtree::kFront>(tree);
|
||||
}
|
||||
|
||||
inline CordRep* CordRepBtreeNavigator::InitLast(CordRepBtree* tree) {
|
||||
return Init<CordRepBtree::kBack>(tree);
|
||||
}
|
||||
|
||||
template <CordRepBtree::EdgeType edge_type>
|
||||
inline CordRep* CordRepBtreeNavigator::Init(CordRepBtree* tree) {
|
||||
assert(tree != nullptr);
|
||||
assert(tree->size() > 0);
|
||||
assert(tree->height() <= CordRepBtree::kMaxHeight);
|
||||
int height = height_ = tree->height();
|
||||
size_t index = tree->index(edge_type);
|
||||
node_[height] = tree;
|
||||
index_[height] = static_cast<uint8_t>(index);
|
||||
while (--height >= 0) {
|
||||
tree = tree->Edge(index)->btree();
|
||||
node_[height] = tree;
|
||||
index = tree->index(edge_type);
|
||||
index_[height] = static_cast<uint8_t>(index);
|
||||
}
|
||||
return node_[0]->Edge(index);
|
||||
}
|
||||
|
||||
inline CordRepBtreeNavigator::Position CordRepBtreeNavigator::Seek(
|
||||
size_t offset) {
|
||||
assert(btree() != nullptr);
|
||||
int height = height_;
|
||||
CordRepBtree* edge = node_[height];
|
||||
if (ABSL_PREDICT_FALSE(offset >= edge->length)) return {nullptr, 0};
|
||||
CordRepBtree::Position index = edge->IndexOf(offset);
|
||||
index_[height] = static_cast<uint8_t>(index.index);
|
||||
while (--height >= 0) {
|
||||
edge = edge->Edge(index.index)->btree();
|
||||
node_[height] = edge;
|
||||
index = edge->IndexOf(index.n);
|
||||
index_[height] = static_cast<uint8_t>(index.index);
|
||||
}
|
||||
return {edge->Edge(index.index), index.n};
|
||||
}
|
||||
|
||||
inline CordRepBtreeNavigator::Position CordRepBtreeNavigator::InitOffset(
|
||||
CordRepBtree* tree, size_t offset) {
|
||||
assert(tree != nullptr);
|
||||
assert(tree->height() <= CordRepBtree::kMaxHeight);
|
||||
if (ABSL_PREDICT_FALSE(offset >= tree->length)) return {nullptr, 0};
|
||||
height_ = tree->height();
|
||||
node_[height_] = tree;
|
||||
return Seek(offset);
|
||||
}
|
||||
|
||||
inline CordRep* CordRepBtreeNavigator::Next() {
|
||||
CordRepBtree* edge = node_[0];
|
||||
return index_[0] == edge->back() ? NextUp() : edge->Edge(++index_[0]);
|
||||
}
|
||||
|
||||
inline CordRep* CordRepBtreeNavigator::Previous() {
|
||||
CordRepBtree* edge = node_[0];
|
||||
return index_[0] == edge->begin() ? PreviousUp() : edge->Edge(--index_[0]);
|
||||
}
|
||||
|
||||
inline CordRep* CordRepBtreeNavigator::NextUp() {
|
||||
assert(index_[0] == node_[0]->back());
|
||||
CordRepBtree* edge;
|
||||
size_t index;
|
||||
int height = 0;
|
||||
do {
|
||||
if (++height > height_) return nullptr;
|
||||
edge = node_[height];
|
||||
index = index_[height] + 1;
|
||||
} while (index == edge->end());
|
||||
index_[height] = static_cast<uint8_t>(index);
|
||||
do {
|
||||
node_[--height] = edge = edge->Edge(index)->btree();
|
||||
index_[height] = static_cast<uint8_t>(index = edge->begin());
|
||||
} while (height > 0);
|
||||
return edge->Edge(index);
|
||||
}
|
||||
|
||||
inline CordRep* CordRepBtreeNavigator::PreviousUp() {
|
||||
assert(index_[0] == node_[0]->begin());
|
||||
CordRepBtree* edge;
|
||||
size_t index;
|
||||
int height = 0;
|
||||
do {
|
||||
if (++height > height_) return nullptr;
|
||||
edge = node_[height];
|
||||
index = index_[height];
|
||||
} while (index == edge->begin());
|
||||
index_[height] = static_cast<uint8_t>(--index);
|
||||
do {
|
||||
node_[--height] = edge = edge->Edge(index)->btree();
|
||||
index_[height] = static_cast<uint8_t>(index = edge->back());
|
||||
} while (height > 0);
|
||||
return edge->Edge(index);
|
||||
}
|
||||
|
||||
} // namespace cord_internal
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
|
||||
#endif // ABSL_STRINGS_INTERNAL_CORD_REP_BTREE_NAVIGATOR_H_
|
||||
69
Pods/abseil/absl/strings/internal/cord_rep_btree_reader.cc
generated
Normal file
69
Pods/abseil/absl/strings/internal/cord_rep_btree_reader.cc
generated
Normal file
@@ -0,0 +1,69 @@
|
||||
// Copyright 2021 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/cord_rep_btree_reader.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include "absl/base/config.h"
|
||||
#include "absl/strings/internal/cord_data_edge.h"
|
||||
#include "absl/strings/internal/cord_internal.h"
|
||||
#include "absl/strings/internal/cord_rep_btree.h"
|
||||
#include "absl/strings/internal/cord_rep_btree_navigator.h"
|
||||
#include "absl/strings/internal/cord_rep_flat.h"
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace cord_internal {
|
||||
|
||||
absl::string_view CordRepBtreeReader::Read(size_t n, size_t chunk_size,
|
||||
CordRep*& tree) {
|
||||
assert(chunk_size <= navigator_.Current()->length);
|
||||
|
||||
// If chunk_size is non-zero, we need to start inside last returned edge.
|
||||
// Else we start reading at the next data edge of the tree.
|
||||
CordRep* edge = chunk_size ? navigator_.Current() : navigator_.Next();
|
||||
const size_t offset = chunk_size ? edge->length - chunk_size : 0;
|
||||
|
||||
// Read the sub tree and verify we got what we wanted.
|
||||
ReadResult result = navigator_.Read(offset, n);
|
||||
tree = result.tree;
|
||||
|
||||
// If the data returned in `tree` was covered entirely by `chunk_size`, i.e.,
|
||||
// read from the 'previous' edge, we did not consume any additional data, and
|
||||
// can directly return the substring into the current data edge as the next
|
||||
// chunk. We can easily establish from the above code that `navigator_.Next()`
|
||||
// has not been called as that requires `chunk_size` to be zero.
|
||||
if (n < chunk_size) return EdgeData(edge).substr(result.n);
|
||||
|
||||
// The amount of data taken from the last edge is `chunk_size` and `result.n`
|
||||
// contains the offset into the current edge trailing the read data (which can
|
||||
// be 0). As the call to `navigator_.Read()` could have consumed all remaining
|
||||
// data, calling `navigator_.Current()` is not safe before checking if we
|
||||
// already consumed all remaining data.
|
||||
const size_t consumed_by_read = n - chunk_size - result.n;
|
||||
if (consumed_by_read >= remaining_) {
|
||||
remaining_ = 0;
|
||||
return {};
|
||||
}
|
||||
|
||||
// We did not read all data, return remaining data from current edge.
|
||||
edge = navigator_.Current();
|
||||
remaining_ -= consumed_by_read + edge->length;
|
||||
return EdgeData(edge).substr(result.n);
|
||||
}
|
||||
|
||||
} // namespace cord_internal
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
212
Pods/abseil/absl/strings/internal/cord_rep_btree_reader.h
generated
Normal file
212
Pods/abseil/absl/strings/internal/cord_rep_btree_reader.h
generated
Normal file
@@ -0,0 +1,212 @@
|
||||
// Copyright 2021 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_CORD_REP_BTREE_READER_H_
|
||||
#define ABSL_STRINGS_INTERNAL_CORD_REP_BTREE_READER_H_
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include "absl/base/config.h"
|
||||
#include "absl/strings/internal/cord_data_edge.h"
|
||||
#include "absl/strings/internal/cord_internal.h"
|
||||
#include "absl/strings/internal/cord_rep_btree.h"
|
||||
#include "absl/strings/internal/cord_rep_btree_navigator.h"
|
||||
#include "absl/strings/internal/cord_rep_flat.h"
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace cord_internal {
|
||||
|
||||
// CordRepBtreeReader implements logic to iterate over cord btrees.
|
||||
// References to the underlying data are returned as absl::string_view values.
|
||||
// The most typical use case is a forward only iteration over tree data.
|
||||
// The class also provides `Skip()`, `Seek()` and `Read()` methods similar to
|
||||
// CordRepBtreeNavigator that allow more advanced navigation.
|
||||
//
|
||||
// Example: iterate over all data inside a cord btree:
|
||||
//
|
||||
// CordRepBtreeReader reader;
|
||||
// for (string_view sv = reader.Init(tree); !sv.Empty(); sv = sv.Next()) {
|
||||
// DoSomethingWithDataIn(sv);
|
||||
// }
|
||||
//
|
||||
// All navigation methods always return the next 'chunk' of data. The class
|
||||
// assumes that all data is directly 'consumed' by the caller. For example:
|
||||
// invoking `Skip()` will skip the desired number of bytes, and directly
|
||||
// read and return the next chunk of data directly after the skipped bytes.
|
||||
//
|
||||
// Example: iterate over all data inside a btree skipping the first 100 bytes:
|
||||
//
|
||||
// CordRepBtreeReader reader;
|
||||
// absl::string_view sv = reader.Init(tree);
|
||||
// if (sv.length() > 100) {
|
||||
// sv.RemovePrefix(100);
|
||||
// } else {
|
||||
// sv = reader.Skip(100 - sv.length());
|
||||
// }
|
||||
// while (!sv.empty()) {
|
||||
// DoSomethingWithDataIn(sv);
|
||||
// absl::string_view sv = reader.Next();
|
||||
// }
|
||||
//
|
||||
// It is important to notice that `remaining` is based on the end position of
|
||||
// the last data edge returned to the caller, not the cumulative data returned
|
||||
// to the caller which can be less in cases of skipping or seeking over data.
|
||||
//
|
||||
// For example, consider a cord btree with five data edges: "abc", "def", "ghi",
|
||||
// "jkl" and "mno":
|
||||
//
|
||||
// absl::string_view sv;
|
||||
// CordRepBtreeReader reader;
|
||||
//
|
||||
// sv = reader.Init(tree); // sv = "abc", remaining = 12
|
||||
// sv = reader.Skip(4); // sv = "hi", remaining = 6
|
||||
// sv = reader.Skip(2); // sv = "l", remaining = 3
|
||||
// sv = reader.Next(); // sv = "mno", remaining = 0
|
||||
// sv = reader.Seek(1); // sv = "bc", remaining = 12
|
||||
//
|
||||
class CordRepBtreeReader {
|
||||
public:
|
||||
using ReadResult = CordRepBtreeNavigator::ReadResult;
|
||||
using Position = CordRepBtreeNavigator::Position;
|
||||
|
||||
// Returns true if this instance is not empty.
|
||||
explicit operator bool() const { return navigator_.btree() != nullptr; }
|
||||
|
||||
// Returns the tree referenced by this instance or nullptr if empty.
|
||||
CordRepBtree* btree() const { return navigator_.btree(); }
|
||||
|
||||
// Returns the current data edge inside the referenced btree.
|
||||
// Requires that the current instance is not empty.
|
||||
CordRep* node() const { return navigator_.Current(); }
|
||||
|
||||
// Returns the length of the referenced tree.
|
||||
// Requires that the current instance is not empty.
|
||||
size_t length() const;
|
||||
|
||||
// Returns the number of remaining bytes available for iteration, which is the
|
||||
// number of bytes directly following the end of the last chunk returned.
|
||||
// This value will be zero if we iterated over the last edge in the bound
|
||||
// tree, in which case any call to Next() or Skip() will return an empty
|
||||
// string_view reflecting the EOF state.
|
||||
// Note that a call to `Seek()` resets `remaining` to a value based on the
|
||||
// end position of the chunk returned by that call.
|
||||
size_t remaining() const { return remaining_; }
|
||||
|
||||
// Resets this instance to an empty value.
|
||||
void Reset() { navigator_.Reset(); }
|
||||
|
||||
// Initializes this instance with `tree`. `tree` must not be null.
|
||||
// Returns a reference to the first data edge of the provided tree.
|
||||
absl::string_view Init(CordRepBtree* tree);
|
||||
|
||||
// Navigates to and returns the next data edge of the referenced tree.
|
||||
// Returns an empty string_view if an attempt is made to read beyond the end
|
||||
// of the tree, i.e.: if `remaining()` is zero indicating an EOF condition.
|
||||
// Requires that the current instance is not empty.
|
||||
absl::string_view Next();
|
||||
|
||||
// Skips the provided amount of bytes and returns a reference to the data
|
||||
// directly following the skipped bytes.
|
||||
absl::string_view Skip(size_t skip);
|
||||
|
||||
// Reads `n` bytes into `tree`.
|
||||
// If `chunk_size` is zero, starts reading at the next data edge. If
|
||||
// `chunk_size` is non zero, the read starts at the last `chunk_size` bytes of
|
||||
// the last returned data edge. Effectively, this means that the read starts
|
||||
// at offset `consumed() - chunk_size`.
|
||||
// Requires that `chunk_size` is less than or equal to the length of the
|
||||
// last returned data edge. The purpose of `chunk_size` is to simplify code
|
||||
// partially consuming a returned chunk and wanting to include the remaining
|
||||
// bytes in the Read call. For example, the below code will read 1000 bytes of
|
||||
// data into a cord tree if the first chunk starts with "big:":
|
||||
//
|
||||
// CordRepBtreeReader reader;
|
||||
// absl::string_view sv = reader.Init(tree);
|
||||
// if (absl::StartsWith(sv, "big:")) {
|
||||
// CordRepBtree tree;
|
||||
// sv = reader.Read(1000, sv.size() - 4 /* "big:" */, &tree);
|
||||
// }
|
||||
//
|
||||
// This method will return an empty string view if all remaining data was
|
||||
// read. If `n` exceeded the amount of remaining data this function will
|
||||
// return an empty string view and `tree` will be set to nullptr.
|
||||
// In both cases, `consumed` will be set to `length`.
|
||||
absl::string_view Read(size_t n, size_t chunk_size, CordRep*& tree);
|
||||
|
||||
// Navigates to the chunk at offset `offset`.
|
||||
// Returns a reference into the navigated to chunk, adjusted for the relative
|
||||
// position of `offset` into that chunk. For example, calling `Seek(13)` on a
|
||||
// cord tree containing 2 chunks of 10 and 20 bytes respectively will return
|
||||
// a string view into the second chunk starting at offset 3 with a size of 17.
|
||||
// Returns an empty string view if `offset` is equal to or greater than the
|
||||
// length of the referenced tree.
|
||||
absl::string_view Seek(size_t offset);
|
||||
|
||||
private:
|
||||
size_t remaining_ = 0;
|
||||
CordRepBtreeNavigator navigator_;
|
||||
};
|
||||
|
||||
inline size_t CordRepBtreeReader::length() const {
|
||||
assert(btree() != nullptr);
|
||||
return btree()->length;
|
||||
}
|
||||
|
||||
inline absl::string_view CordRepBtreeReader::Init(CordRepBtree* tree) {
|
||||
assert(tree != nullptr);
|
||||
const CordRep* edge = navigator_.InitFirst(tree);
|
||||
remaining_ = tree->length - edge->length;
|
||||
return EdgeData(edge);
|
||||
}
|
||||
|
||||
inline absl::string_view CordRepBtreeReader::Next() {
|
||||
if (remaining_ == 0) return {};
|
||||
const CordRep* edge = navigator_.Next();
|
||||
assert(edge != nullptr);
|
||||
remaining_ -= edge->length;
|
||||
return EdgeData(edge);
|
||||
}
|
||||
|
||||
inline absl::string_view CordRepBtreeReader::Skip(size_t skip) {
|
||||
// As we are always positioned on the last 'consumed' edge, we
|
||||
// need to skip the current edge as well as `skip`.
|
||||
const size_t edge_length = navigator_.Current()->length;
|
||||
CordRepBtreeNavigator::Position pos = navigator_.Skip(skip + edge_length);
|
||||
if (ABSL_PREDICT_FALSE(pos.edge == nullptr)) {
|
||||
remaining_ = 0;
|
||||
return {};
|
||||
}
|
||||
// The combined length of all edges skipped before `pos.edge` is `skip -
|
||||
// pos.offset`, all of which are 'consumed', as well as the current edge.
|
||||
remaining_ -= skip - pos.offset + pos.edge->length;
|
||||
return EdgeData(pos.edge).substr(pos.offset);
|
||||
}
|
||||
|
||||
inline absl::string_view CordRepBtreeReader::Seek(size_t offset) {
|
||||
const CordRepBtreeNavigator::Position pos = navigator_.Seek(offset);
|
||||
if (ABSL_PREDICT_FALSE(pos.edge == nullptr)) {
|
||||
remaining_ = 0;
|
||||
return {};
|
||||
}
|
||||
absl::string_view chunk = EdgeData(pos.edge).substr(pos.offset);
|
||||
remaining_ = length() - offset - chunk.length();
|
||||
return chunk;
|
||||
}
|
||||
|
||||
} // namespace cord_internal
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
|
||||
#endif // ABSL_STRINGS_INTERNAL_CORD_REP_BTREE_READER_H_
|
||||
64
Pods/abseil/absl/strings/internal/cord_rep_consume.cc
generated
Normal file
64
Pods/abseil/absl/strings/internal/cord_rep_consume.cc
generated
Normal file
@@ -0,0 +1,64 @@
|
||||
// Copyright 2021 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/cord_rep_consume.h"
|
||||
|
||||
#include <array>
|
||||
#include <utility>
|
||||
|
||||
#include "absl/container/inlined_vector.h"
|
||||
#include "absl/functional/function_ref.h"
|
||||
#include "absl/strings/internal/cord_internal.h"
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace cord_internal {
|
||||
|
||||
namespace {
|
||||
|
||||
// Unrefs the provided `substring`, and returns `substring->child`
|
||||
// Adds or assumes a reference on `substring->child`
|
||||
CordRep* ClipSubstring(CordRepSubstring* substring) {
|
||||
CordRep* child = substring->child;
|
||||
if (substring->refcount.IsOne()) {
|
||||
delete substring;
|
||||
} else {
|
||||
CordRep::Ref(child);
|
||||
CordRep::Unref(substring);
|
||||
}
|
||||
return child;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void Consume(CordRep* rep,
|
||||
FunctionRef<void(CordRep*, size_t, size_t)> consume_fn) {
|
||||
size_t offset = 0;
|
||||
size_t length = rep->length;
|
||||
|
||||
if (rep->tag == SUBSTRING) {
|
||||
offset += rep->substring()->start;
|
||||
rep = ClipSubstring(rep->substring());
|
||||
}
|
||||
consume_fn(rep, offset, length);
|
||||
}
|
||||
|
||||
void ReverseConsume(CordRep* rep,
|
||||
FunctionRef<void(CordRep*, size_t, size_t)> consume_fn) {
|
||||
return Consume(rep, consume_fn);
|
||||
}
|
||||
|
||||
} // namespace cord_internal
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
47
Pods/abseil/absl/strings/internal/cord_rep_consume.h
generated
Normal file
47
Pods/abseil/absl/strings/internal/cord_rep_consume.h
generated
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright 2021 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_CORD_REP_CONSUME_H_
|
||||
#define ABSL_STRINGS_INTERNAL_CORD_REP_CONSUME_H_
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "absl/functional/function_ref.h"
|
||||
#include "absl/strings/internal/cord_internal.h"
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace cord_internal {
|
||||
|
||||
// Consume() and ReverseConsume() consume CONCAT based trees and invoke the
|
||||
// provided functor with the contained nodes in the proper forward or reverse
|
||||
// order, which is used to convert CONCAT trees into other tree or cord data.
|
||||
// All CONCAT and SUBSTRING nodes are processed internally. The 'offset`
|
||||
// parameter of the functor is non-zero for any nodes below SUBSTRING nodes.
|
||||
// It's up to the caller to form these back into SUBSTRING nodes or otherwise
|
||||
// store offset / prefix information. These functions are intended to be used
|
||||
// only for migration / transitional code where due to factors such as ODR
|
||||
// violations, we can not 100% guarantee that all code respects 'new format'
|
||||
// settings and flags, so we need to be able to parse old data on the fly until
|
||||
// all old code is deprecated / no longer the default format.
|
||||
void Consume(CordRep* rep,
|
||||
FunctionRef<void(CordRep*, size_t, size_t)> consume_fn);
|
||||
void ReverseConsume(CordRep* rep,
|
||||
FunctionRef<void(CordRep*, size_t, size_t)> consume_fn);
|
||||
|
||||
} // namespace cord_internal
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
|
||||
#endif // ABSL_STRINGS_INTERNAL_CORD_REP_CONSUME_H_
|
||||
56
Pods/abseil/absl/strings/internal/cord_rep_crc.cc
generated
Normal file
56
Pods/abseil/absl/strings/internal/cord_rep_crc.cc
generated
Normal file
@@ -0,0 +1,56 @@
|
||||
// Copyright 2021 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/cord_rep_crc.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <utility>
|
||||
|
||||
#include "absl/base/config.h"
|
||||
#include "absl/strings/internal/cord_internal.h"
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace cord_internal {
|
||||
|
||||
CordRepCrc* CordRepCrc::New(CordRep* child, crc_internal::CrcCordState state) {
|
||||
if (child != nullptr && child->IsCrc()) {
|
||||
if (child->refcount.IsOne()) {
|
||||
child->crc()->crc_cord_state = std::move(state);
|
||||
return child->crc();
|
||||
}
|
||||
CordRep* old = child;
|
||||
child = old->crc()->child;
|
||||
CordRep::Ref(child);
|
||||
CordRep::Unref(old);
|
||||
}
|
||||
auto* new_cordrep = new CordRepCrc;
|
||||
new_cordrep->length = child != nullptr ? child->length : 0;
|
||||
new_cordrep->tag = cord_internal::CRC;
|
||||
new_cordrep->child = child;
|
||||
new_cordrep->crc_cord_state = std::move(state);
|
||||
return new_cordrep;
|
||||
}
|
||||
|
||||
void CordRepCrc::Destroy(CordRepCrc* node) {
|
||||
if (node->child != nullptr) {
|
||||
CordRep::Unref(node->child);
|
||||
}
|
||||
delete node;
|
||||
}
|
||||
|
||||
} // namespace cord_internal
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
103
Pods/abseil/absl/strings/internal/cord_rep_crc.h
generated
Normal file
103
Pods/abseil/absl/strings/internal/cord_rep_crc.h
generated
Normal file
@@ -0,0 +1,103 @@
|
||||
// Copyright 2021 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_CORD_REP_CRC_H_
|
||||
#define ABSL_STRINGS_INTERNAL_CORD_REP_CRC_H_
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
|
||||
#include "absl/base/config.h"
|
||||
#include "absl/base/optimization.h"
|
||||
#include "absl/crc/internal/crc_cord_state.h"
|
||||
#include "absl/strings/internal/cord_internal.h"
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace cord_internal {
|
||||
|
||||
// CordRepCrc is a CordRep node intended only to appear at the top level of a
|
||||
// cord tree. It associates an "expected CRC" with the contained data, to allow
|
||||
// for easy passage of checksum data in Cord data flows.
|
||||
//
|
||||
// From Cord's perspective, the crc value has no semantics; any validation of
|
||||
// the contained checksum is the user's responsibility.
|
||||
struct CordRepCrc : public CordRep {
|
||||
CordRep* child;
|
||||
absl::crc_internal::CrcCordState crc_cord_state;
|
||||
|
||||
// Consumes `child` and returns a CordRepCrc prefixed tree containing `child`.
|
||||
// If the specified `child` is itself a CordRepCrc node, then this method
|
||||
// either replaces the existing node, or directly updates the crc state in it
|
||||
// depending on the node being shared or not, i.e.: refcount.IsOne().
|
||||
// `child` must only be null if the Cord is empty. Never returns null.
|
||||
static CordRepCrc* New(CordRep* child, crc_internal::CrcCordState state);
|
||||
|
||||
// Destroys (deletes) the provided node. `node` must not be null.
|
||||
static void Destroy(CordRepCrc* node);
|
||||
};
|
||||
|
||||
// Consumes `rep` and returns a CordRep* with any outer CordRepCrc wrapper
|
||||
// removed. This is usually a no-op (returning `rep`), but this will remove and
|
||||
// unref an outer CordRepCrc node.
|
||||
inline CordRep* RemoveCrcNode(CordRep* rep) {
|
||||
assert(rep != nullptr);
|
||||
if (ABSL_PREDICT_FALSE(rep->IsCrc())) {
|
||||
CordRep* child = rep->crc()->child;
|
||||
if (rep->refcount.IsOne()) {
|
||||
delete rep->crc();
|
||||
} else {
|
||||
CordRep::Ref(child);
|
||||
CordRep::Unref(rep);
|
||||
}
|
||||
return child;
|
||||
}
|
||||
return rep;
|
||||
}
|
||||
|
||||
// Returns `rep` if it is not a CordRepCrc node, or its child if it is.
|
||||
// Does not consume or create a reference on `rep` or the returned value.
|
||||
inline CordRep* SkipCrcNode(CordRep* rep) {
|
||||
assert(rep != nullptr);
|
||||
if (ABSL_PREDICT_FALSE(rep->IsCrc())) {
|
||||
return rep->crc()->child;
|
||||
} else {
|
||||
return rep;
|
||||
}
|
||||
}
|
||||
|
||||
inline const CordRep* SkipCrcNode(const CordRep* rep) {
|
||||
assert(rep != nullptr);
|
||||
if (ABSL_PREDICT_FALSE(rep->IsCrc())) {
|
||||
return rep->crc()->child;
|
||||
} else {
|
||||
return rep;
|
||||
}
|
||||
}
|
||||
|
||||
inline CordRepCrc* CordRep::crc() {
|
||||
assert(IsCrc());
|
||||
return static_cast<CordRepCrc*>(this);
|
||||
}
|
||||
|
||||
inline const CordRepCrc* CordRep::crc() const {
|
||||
assert(IsCrc());
|
||||
return static_cast<const CordRepCrc*>(this);
|
||||
}
|
||||
|
||||
} // namespace cord_internal
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
|
||||
#endif // ABSL_STRINGS_INTERNAL_CORD_REP_CRC_H_
|
||||
195
Pods/abseil/absl/strings/internal/cord_rep_flat.h
generated
Normal file
195
Pods/abseil/absl/strings/internal/cord_rep_flat.h
generated
Normal file
@@ -0,0 +1,195 @@
|
||||
// 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_CORD_REP_FLAT_H_
|
||||
#define ABSL_STRINGS_INTERNAL_CORD_REP_FLAT_H_
|
||||
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
#include "absl/base/config.h"
|
||||
#include "absl/base/macros.h"
|
||||
#include "absl/strings/internal/cord_internal.h"
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace cord_internal {
|
||||
|
||||
// Note: all constants below are never ODR used and internal to cord, we define
|
||||
// these as static constexpr to avoid 'in struct' definition and usage clutter.
|
||||
|
||||
// Largest and smallest flat node lengths we are willing to allocate
|
||||
// Flat allocation size is stored in tag, which currently can encode sizes up
|
||||
// to 4K, encoded as multiple of either 8 or 32 bytes.
|
||||
// If we allow for larger sizes, we need to change this to 8/64, 16/128, etc.
|
||||
// kMinFlatSize is bounded by tag needing to be at least FLAT * 8 bytes, and
|
||||
// ideally a 'nice' size aligning with allocation and cacheline sizes like 32.
|
||||
// kMaxFlatSize is bounded by the size resulting in a computed tag no greater
|
||||
// than MAX_FLAT_TAG. MAX_FLAT_TAG provides for additional 'high' tag values.
|
||||
static constexpr size_t kFlatOverhead = offsetof(CordRep, storage);
|
||||
static constexpr size_t kMinFlatSize = 32;
|
||||
static constexpr size_t kMaxFlatSize = 4096;
|
||||
static constexpr size_t kMaxFlatLength = kMaxFlatSize - kFlatOverhead;
|
||||
static constexpr size_t kMinFlatLength = kMinFlatSize - kFlatOverhead;
|
||||
static constexpr size_t kMaxLargeFlatSize = 256 * 1024;
|
||||
static constexpr size_t kMaxLargeFlatLength = kMaxLargeFlatSize - kFlatOverhead;
|
||||
|
||||
// kTagBase should make the Size <--> Tag computation resilient
|
||||
// against changes to the value of FLAT when we add a new tag..
|
||||
static constexpr uint8_t kTagBase = FLAT - 4;
|
||||
|
||||
// Converts the provided rounded size to the corresponding tag
|
||||
constexpr uint8_t AllocatedSizeToTagUnchecked(size_t size) {
|
||||
return static_cast<uint8_t>(size <= 512 ? kTagBase + size / 8
|
||||
: size <= 8192
|
||||
? kTagBase + 512 / 8 + size / 64 - 512 / 64
|
||||
: kTagBase + 512 / 8 + ((8192 - 512) / 64) +
|
||||
size / 4096 - 8192 / 4096);
|
||||
}
|
||||
|
||||
// Converts the provided tag to the corresponding allocated size
|
||||
constexpr size_t TagToAllocatedSize(uint8_t tag) {
|
||||
return (tag <= kTagBase + 512 / 8) ? tag * 8 - kTagBase * 8
|
||||
: (tag <= kTagBase + (512 / 8) + ((8192 - 512) / 64))
|
||||
? 512 + tag * 64 - kTagBase * 64 - 512 / 8 * 64
|
||||
: 8192 + tag * 4096 - kTagBase * 4096 -
|
||||
((512 / 8) + ((8192 - 512) / 64)) * 4096;
|
||||
}
|
||||
|
||||
static_assert(AllocatedSizeToTagUnchecked(kMinFlatSize) == FLAT, "");
|
||||
static_assert(AllocatedSizeToTagUnchecked(kMaxLargeFlatSize) == MAX_FLAT_TAG,
|
||||
"");
|
||||
|
||||
// RoundUp logically performs `((n + m - 1) / m) * m` to round up to the nearest
|
||||
// multiple of `m`, optimized for the invariant that `m` is a power of 2.
|
||||
constexpr size_t RoundUp(size_t n, size_t m) {
|
||||
return (n + m - 1) & (0 - m);
|
||||
}
|
||||
|
||||
// Returns the size to the nearest equal or larger value that can be
|
||||
// expressed exactly as a tag value.
|
||||
inline size_t RoundUpForTag(size_t size) {
|
||||
return RoundUp(size, (size <= 512) ? 8 : (size <= 8192 ? 64 : 4096));
|
||||
}
|
||||
|
||||
// Converts the allocated size to a tag, rounding down if the size
|
||||
// does not exactly match a 'tag expressible' size value. The result is
|
||||
// undefined if the size exceeds the maximum size that can be encoded in
|
||||
// a tag, i.e., if size is larger than TagToAllocatedSize(<max tag>).
|
||||
inline uint8_t AllocatedSizeToTag(size_t size) {
|
||||
const uint8_t tag = AllocatedSizeToTagUnchecked(size);
|
||||
assert(tag <= MAX_FLAT_TAG);
|
||||
return tag;
|
||||
}
|
||||
|
||||
// Converts the provided tag to the corresponding available data length
|
||||
constexpr size_t TagToLength(uint8_t tag) {
|
||||
return TagToAllocatedSize(tag) - kFlatOverhead;
|
||||
}
|
||||
|
||||
// Enforce that kMaxFlatSize maps to a well-known exact tag value.
|
||||
static_assert(TagToAllocatedSize(MAX_FLAT_TAG) == kMaxLargeFlatSize,
|
||||
"Bad tag logic");
|
||||
|
||||
struct CordRepFlat : public CordRep {
|
||||
// Tag for explicit 'large flat' allocation
|
||||
struct Large {};
|
||||
|
||||
// Creates a new flat node.
|
||||
template <size_t max_flat_size, typename... Args>
|
||||
static CordRepFlat* NewImpl(size_t len, Args... args ABSL_ATTRIBUTE_UNUSED) {
|
||||
if (len <= kMinFlatLength) {
|
||||
len = kMinFlatLength;
|
||||
} else if (len > max_flat_size - kFlatOverhead) {
|
||||
len = max_flat_size - kFlatOverhead;
|
||||
}
|
||||
|
||||
// Round size up so it matches a size we can exactly express in a tag.
|
||||
const size_t size = RoundUpForTag(len + kFlatOverhead);
|
||||
void* const raw_rep = ::operator new(size);
|
||||
// GCC 13 has a false-positive -Wstringop-overflow warning here.
|
||||
#if ABSL_INTERNAL_HAVE_MIN_GNUC_VERSION(13, 0)
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wstringop-overflow"
|
||||
#endif
|
||||
CordRepFlat* rep = new (raw_rep) CordRepFlat();
|
||||
rep->tag = AllocatedSizeToTag(size);
|
||||
#if ABSL_INTERNAL_HAVE_MIN_GNUC_VERSION(13, 0)
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
return rep;
|
||||
}
|
||||
|
||||
static CordRepFlat* New(size_t len) { return NewImpl<kMaxFlatSize>(len); }
|
||||
|
||||
static CordRepFlat* New(Large, size_t len) {
|
||||
return NewImpl<kMaxLargeFlatSize>(len);
|
||||
}
|
||||
|
||||
// Deletes a CordRepFlat instance created previously through a call to New().
|
||||
// Flat CordReps are allocated and constructed with raw ::operator new and
|
||||
// placement new, and must be destructed and deallocated accordingly.
|
||||
static void Delete(CordRep*rep) {
|
||||
assert(rep->tag >= FLAT && rep->tag <= MAX_FLAT_TAG);
|
||||
|
||||
#if defined(__cpp_sized_deallocation)
|
||||
size_t size = TagToAllocatedSize(rep->tag);
|
||||
rep->~CordRep();
|
||||
::operator delete(rep, size);
|
||||
#else
|
||||
rep->~CordRep();
|
||||
::operator delete(rep);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Create a CordRepFlat containing `data`, with an optional additional
|
||||
// extra capacity of up to `extra` bytes. Requires that `data.size()`
|
||||
// is less than kMaxFlatLength.
|
||||
static CordRepFlat* Create(absl::string_view data, size_t extra = 0) {
|
||||
assert(data.size() <= kMaxFlatLength);
|
||||
CordRepFlat* flat = New(data.size() + (std::min)(extra, kMaxFlatLength));
|
||||
memcpy(flat->Data(), data.data(), data.size());
|
||||
flat->length = data.size();
|
||||
return flat;
|
||||
}
|
||||
|
||||
// Returns a pointer to the data inside this flat rep.
|
||||
char* Data() { return reinterpret_cast<char*>(storage); }
|
||||
const char* Data() const { return reinterpret_cast<const char*>(storage); }
|
||||
|
||||
// Returns the maximum capacity (payload size) of this instance.
|
||||
size_t Capacity() const { return TagToLength(tag); }
|
||||
|
||||
// Returns the allocated size (payload + overhead) of this instance.
|
||||
size_t AllocatedSize() const { return TagToAllocatedSize(tag); }
|
||||
};
|
||||
|
||||
// Now that CordRepFlat is defined, we can define CordRep's helper casts:
|
||||
inline CordRepFlat* CordRep::flat() {
|
||||
assert(tag >= FLAT && tag <= MAX_FLAT_TAG);
|
||||
return reinterpret_cast<CordRepFlat*>(this);
|
||||
}
|
||||
|
||||
inline const CordRepFlat* CordRep::flat() const {
|
||||
assert(tag >= FLAT && tag <= MAX_FLAT_TAG);
|
||||
return reinterpret_cast<const CordRepFlat*>(this);
|
||||
}
|
||||
|
||||
} // namespace cord_internal
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
|
||||
#endif // ABSL_STRINGS_INTERNAL_CORD_REP_FLAT_H_
|
||||
96
Pods/abseil/absl/strings/internal/cordz_functions.cc
generated
Normal file
96
Pods/abseil/absl/strings/internal/cordz_functions.cc
generated
Normal file
@@ -0,0 +1,96 @@
|
||||
// Copyright 2019 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/cordz_functions.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <random>
|
||||
|
||||
#include "absl/base/attributes.h"
|
||||
#include "absl/base/config.h"
|
||||
#include "absl/base/internal/raw_logging.h"
|
||||
#include "absl/profiling/internal/exponential_biased.h"
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace cord_internal {
|
||||
namespace {
|
||||
|
||||
// The average interval until the next sample. A value of 0 disables profiling
|
||||
// while a value of 1 will profile all Cords.
|
||||
std::atomic<int> g_cordz_mean_interval(50000);
|
||||
|
||||
} // namespace
|
||||
|
||||
#ifdef ABSL_INTERNAL_CORDZ_ENABLED
|
||||
|
||||
// Special negative 'not initialized' per thread value for cordz_next_sample.
|
||||
static constexpr int64_t kInitCordzNextSample = -1;
|
||||
|
||||
ABSL_CONST_INIT thread_local int64_t cordz_next_sample = kInitCordzNextSample;
|
||||
|
||||
// kIntervalIfDisabled is the number of profile-eligible events need to occur
|
||||
// before the code will confirm that cordz is still disabled.
|
||||
constexpr int64_t kIntervalIfDisabled = 1 << 16;
|
||||
|
||||
ABSL_ATTRIBUTE_NOINLINE bool cordz_should_profile_slow() {
|
||||
|
||||
thread_local absl::profiling_internal::ExponentialBiased
|
||||
exponential_biased_generator;
|
||||
int32_t mean_interval = get_cordz_mean_interval();
|
||||
|
||||
// Check if we disabled profiling. If so, set the next sample to a "large"
|
||||
// number to minimize the overhead of the should_profile codepath.
|
||||
if (mean_interval <= 0) {
|
||||
cordz_next_sample = kIntervalIfDisabled;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if we're always sampling.
|
||||
if (mean_interval == 1) {
|
||||
cordz_next_sample = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (cordz_next_sample <= 0) {
|
||||
// If first check on current thread, check cordz_should_profile()
|
||||
// again using the created (initial) stride in cordz_next_sample.
|
||||
const bool initialized = cordz_next_sample != kInitCordzNextSample;
|
||||
cordz_next_sample = exponential_biased_generator.GetStride(mean_interval);
|
||||
return initialized || cordz_should_profile();
|
||||
}
|
||||
|
||||
--cordz_next_sample;
|
||||
return false;
|
||||
}
|
||||
|
||||
void cordz_set_next_sample_for_testing(int64_t next_sample) {
|
||||
cordz_next_sample = next_sample;
|
||||
}
|
||||
|
||||
#endif // ABSL_INTERNAL_CORDZ_ENABLED
|
||||
|
||||
int32_t get_cordz_mean_interval() {
|
||||
return g_cordz_mean_interval.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
void set_cordz_mean_interval(int32_t mean_interval) {
|
||||
g_cordz_mean_interval.store(mean_interval, std::memory_order_release);
|
||||
}
|
||||
|
||||
} // namespace cord_internal
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
77
Pods/abseil/absl/strings/internal/cordz_functions.h
generated
Normal file
77
Pods/abseil/absl/strings/internal/cordz_functions.h
generated
Normal file
@@ -0,0 +1,77 @@
|
||||
// Copyright 2019 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_CORDZ_FUNCTIONS_H_
|
||||
#define ABSL_STRINGS_INTERNAL_CORDZ_FUNCTIONS_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "absl/base/attributes.h"
|
||||
#include "absl/base/config.h"
|
||||
#include "absl/base/optimization.h"
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace cord_internal {
|
||||
|
||||
// Returns the current sample rate. This represents the average interval
|
||||
// between samples.
|
||||
int32_t get_cordz_mean_interval();
|
||||
|
||||
// Sets the sample rate with the average interval between samples.
|
||||
void set_cordz_mean_interval(int32_t mean_interval);
|
||||
|
||||
// Cordz is only enabled on Linux with thread_local support.
|
||||
#if defined(ABSL_INTERNAL_CORDZ_ENABLED)
|
||||
#error ABSL_INTERNAL_CORDZ_ENABLED cannot be set directly
|
||||
#elif defined(__linux__) && defined(ABSL_HAVE_THREAD_LOCAL)
|
||||
#define ABSL_INTERNAL_CORDZ_ENABLED 1
|
||||
#endif
|
||||
|
||||
#ifdef ABSL_INTERNAL_CORDZ_ENABLED
|
||||
|
||||
// cordz_next_sample is the number of events until the next sample event. If
|
||||
// the value is 1 or less, the code will check on the next event if cordz is
|
||||
// enabled, and if so, will sample the Cord. cordz is only enabled when we can
|
||||
// use thread locals.
|
||||
ABSL_CONST_INIT extern thread_local int64_t cordz_next_sample;
|
||||
|
||||
// Determines if the next sample should be profiled. If it is, the value pointed
|
||||
// at by next_sample will be set with the interval until the next sample.
|
||||
bool cordz_should_profile_slow();
|
||||
|
||||
// Returns true if the next cord should be sampled.
|
||||
inline bool cordz_should_profile() {
|
||||
if (ABSL_PREDICT_TRUE(cordz_next_sample > 1)) {
|
||||
cordz_next_sample--;
|
||||
return false;
|
||||
}
|
||||
return cordz_should_profile_slow();
|
||||
}
|
||||
|
||||
// Sets the interval until the next sample (for testing only)
|
||||
void cordz_set_next_sample_for_testing(int64_t next_sample);
|
||||
|
||||
#else // ABSL_INTERNAL_CORDZ_ENABLED
|
||||
|
||||
inline bool cordz_should_profile() { return false; }
|
||||
inline void cordz_set_next_sample_for_testing(int64_t) {}
|
||||
|
||||
#endif // ABSL_INTERNAL_CORDZ_ENABLED
|
||||
|
||||
} // namespace cord_internal
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
|
||||
#endif // ABSL_STRINGS_INTERNAL_CORDZ_FUNCTIONS_H_
|
||||
165
Pods/abseil/absl/strings/internal/cordz_handle.cc
generated
Normal file
165
Pods/abseil/absl/strings/internal/cordz_handle.cc
generated
Normal file
@@ -0,0 +1,165 @@
|
||||
// Copyright 2019 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/cordz_handle.h"
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include "absl/base/internal/raw_logging.h" // For ABSL_RAW_CHECK
|
||||
#include "absl/synchronization/mutex.h"
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace cord_internal {
|
||||
|
||||
namespace {
|
||||
|
||||
struct Queue {
|
||||
Queue() = default;
|
||||
|
||||
absl::Mutex mutex;
|
||||
std::atomic<CordzHandle*> dq_tail ABSL_GUARDED_BY(mutex){nullptr};
|
||||
|
||||
// Returns true if this delete queue is empty. This method does not acquire
|
||||
// the lock, but does a 'load acquire' observation on the delete queue tail.
|
||||
// It is used inside Delete() to check for the presence of a delete queue
|
||||
// without holding the lock. The assumption is that the caller is in the
|
||||
// state of 'being deleted', and can not be newly discovered by a concurrent
|
||||
// 'being constructed' snapshot instance. Practically, this means that any
|
||||
// such discovery (`find`, 'first' or 'next', etc) must have proper 'happens
|
||||
// before / after' semantics and atomic fences.
|
||||
bool IsEmpty() const ABSL_NO_THREAD_SAFETY_ANALYSIS {
|
||||
return dq_tail.load(std::memory_order_acquire) == nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
static Queue* GlobalQueue() {
|
||||
static Queue* global_queue = new Queue;
|
||||
return global_queue;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
CordzHandle::CordzHandle(bool is_snapshot) : is_snapshot_(is_snapshot) {
|
||||
Queue* global_queue = GlobalQueue();
|
||||
if (is_snapshot) {
|
||||
MutexLock lock(&global_queue->mutex);
|
||||
CordzHandle* dq_tail =
|
||||
global_queue->dq_tail.load(std::memory_order_acquire);
|
||||
if (dq_tail != nullptr) {
|
||||
dq_prev_ = dq_tail;
|
||||
dq_tail->dq_next_ = this;
|
||||
}
|
||||
global_queue->dq_tail.store(this, std::memory_order_release);
|
||||
}
|
||||
}
|
||||
|
||||
CordzHandle::~CordzHandle() {
|
||||
Queue* global_queue = GlobalQueue();
|
||||
if (is_snapshot_) {
|
||||
std::vector<CordzHandle*> to_delete;
|
||||
{
|
||||
MutexLock lock(&global_queue->mutex);
|
||||
CordzHandle* next = dq_next_;
|
||||
if (dq_prev_ == nullptr) {
|
||||
// We were head of the queue, delete every CordzHandle until we reach
|
||||
// either the end of the list, or a snapshot handle.
|
||||
while (next && !next->is_snapshot_) {
|
||||
to_delete.push_back(next);
|
||||
next = next->dq_next_;
|
||||
}
|
||||
} else {
|
||||
// Another CordzHandle existed before this one, don't delete anything.
|
||||
dq_prev_->dq_next_ = next;
|
||||
}
|
||||
if (next) {
|
||||
next->dq_prev_ = dq_prev_;
|
||||
} else {
|
||||
global_queue->dq_tail.store(dq_prev_, std::memory_order_release);
|
||||
}
|
||||
}
|
||||
for (CordzHandle* handle : to_delete) {
|
||||
delete handle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool CordzHandle::SafeToDelete() const {
|
||||
return is_snapshot_ || GlobalQueue()->IsEmpty();
|
||||
}
|
||||
|
||||
void CordzHandle::Delete(CordzHandle* handle) {
|
||||
assert(handle);
|
||||
if (handle) {
|
||||
Queue* const queue = GlobalQueue();
|
||||
if (!handle->SafeToDelete()) {
|
||||
MutexLock lock(&queue->mutex);
|
||||
CordzHandle* dq_tail = queue->dq_tail.load(std::memory_order_acquire);
|
||||
if (dq_tail != nullptr) {
|
||||
handle->dq_prev_ = dq_tail;
|
||||
dq_tail->dq_next_ = handle;
|
||||
queue->dq_tail.store(handle, std::memory_order_release);
|
||||
return;
|
||||
}
|
||||
}
|
||||
delete handle;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<const CordzHandle*> CordzHandle::DiagnosticsGetDeleteQueue() {
|
||||
std::vector<const CordzHandle*> handles;
|
||||
Queue* global_queue = GlobalQueue();
|
||||
MutexLock lock(&global_queue->mutex);
|
||||
CordzHandle* dq_tail = global_queue->dq_tail.load(std::memory_order_acquire);
|
||||
for (const CordzHandle* p = dq_tail; p; p = p->dq_prev_) {
|
||||
handles.push_back(p);
|
||||
}
|
||||
return handles;
|
||||
}
|
||||
|
||||
bool CordzHandle::DiagnosticsHandleIsSafeToInspect(
|
||||
const CordzHandle* handle) const {
|
||||
if (!is_snapshot_) return false;
|
||||
if (handle == nullptr) return true;
|
||||
if (handle->is_snapshot_) return false;
|
||||
bool snapshot_found = false;
|
||||
Queue* global_queue = GlobalQueue();
|
||||
MutexLock lock(&global_queue->mutex);
|
||||
for (const CordzHandle* p = global_queue->dq_tail; p; p = p->dq_prev_) {
|
||||
if (p == handle) return !snapshot_found;
|
||||
if (p == this) snapshot_found = true;
|
||||
}
|
||||
ABSL_ASSERT(snapshot_found); // Assert that 'this' is in delete queue.
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<const CordzHandle*>
|
||||
CordzHandle::DiagnosticsGetSafeToInspectDeletedHandles() {
|
||||
std::vector<const CordzHandle*> handles;
|
||||
if (!is_snapshot()) {
|
||||
return handles;
|
||||
}
|
||||
|
||||
Queue* global_queue = GlobalQueue();
|
||||
MutexLock lock(&global_queue->mutex);
|
||||
for (const CordzHandle* p = dq_next_; p != nullptr; p = p->dq_next_) {
|
||||
if (!p->is_snapshot()) {
|
||||
handles.push_back(p);
|
||||
}
|
||||
}
|
||||
return handles;
|
||||
}
|
||||
|
||||
} // namespace cord_internal
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
98
Pods/abseil/absl/strings/internal/cordz_handle.h
generated
Normal file
98
Pods/abseil/absl/strings/internal/cordz_handle.h
generated
Normal file
@@ -0,0 +1,98 @@
|
||||
// Copyright 2019 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_CORDZ_HANDLE_H_
|
||||
#define ABSL_STRINGS_INTERNAL_CORDZ_HANDLE_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/base/config.h"
|
||||
#include "absl/base/internal/raw_logging.h"
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace cord_internal {
|
||||
|
||||
// This base class allows multiple types of object (CordzInfo and
|
||||
// CordzSampleToken) to exist simultaneously on the delete queue (pointed to by
|
||||
// global_dq_tail and traversed using dq_prev_ and dq_next_). The
|
||||
// delete queue guarantees that once a profiler creates a CordzSampleToken and
|
||||
// has gained visibility into a CordzInfo object, that CordzInfo object will not
|
||||
// be deleted prematurely. This allows the profiler to inspect all CordzInfo
|
||||
// objects that are alive without needing to hold a global lock.
|
||||
class ABSL_DLL CordzHandle {
|
||||
public:
|
||||
CordzHandle() : CordzHandle(false) {}
|
||||
|
||||
bool is_snapshot() const { return is_snapshot_; }
|
||||
|
||||
// Returns true if this instance is safe to be deleted because it is either a
|
||||
// snapshot, which is always safe to delete, or not included in the global
|
||||
// delete queue and thus not included in any snapshot.
|
||||
// Callers are responsible for making sure this instance can not be newly
|
||||
// discovered by other threads. For example, CordzInfo instances first de-list
|
||||
// themselves from the global CordzInfo list before determining if they are
|
||||
// safe to be deleted directly.
|
||||
// If SafeToDelete returns false, callers MUST use the Delete() method to
|
||||
// safely queue CordzHandle instances for deletion.
|
||||
bool SafeToDelete() const;
|
||||
|
||||
// Deletes the provided instance, or puts it on the delete queue to be deleted
|
||||
// once there are no more sample tokens (snapshot) instances potentially
|
||||
// referencing the instance. `handle` should not be null.
|
||||
static void Delete(CordzHandle* handle);
|
||||
|
||||
// Returns the current entries in the delete queue in LIFO order.
|
||||
static std::vector<const CordzHandle*> DiagnosticsGetDeleteQueue();
|
||||
|
||||
// Returns true if the provided handle is nullptr or guarded by this handle.
|
||||
// Since the CordzSnapshot token is itself a CordzHandle, this method will
|
||||
// allow tests to check if that token is keeping an arbitrary CordzHandle
|
||||
// alive.
|
||||
bool DiagnosticsHandleIsSafeToInspect(const CordzHandle* handle) const;
|
||||
|
||||
// Returns the current entries in the delete queue, in LIFO order, that are
|
||||
// protected by this. CordzHandle objects are only placed on the delete queue
|
||||
// after CordzHandle::Delete is called with them as an argument. Only
|
||||
// CordzHandle objects that are not also CordzSnapshot objects will be
|
||||
// included in the return vector. For each of the handles in the return
|
||||
// vector, the earliest that their memory can be freed is when this
|
||||
// CordzSnapshot object is deleted.
|
||||
std::vector<const CordzHandle*> DiagnosticsGetSafeToInspectDeletedHandles();
|
||||
|
||||
protected:
|
||||
explicit CordzHandle(bool is_snapshot);
|
||||
virtual ~CordzHandle();
|
||||
|
||||
private:
|
||||
const bool is_snapshot_;
|
||||
|
||||
// dq_prev_ and dq_next_ require the global queue mutex to be held.
|
||||
// Unfortunately we can't use thread annotations such that the thread safety
|
||||
// analysis understands that queue_ and global_queue_ are one and the same.
|
||||
CordzHandle* dq_prev_ = nullptr;
|
||||
CordzHandle* dq_next_ = nullptr;
|
||||
};
|
||||
|
||||
class CordzSnapshot : public CordzHandle {
|
||||
public:
|
||||
CordzSnapshot() : CordzHandle(true) {}
|
||||
};
|
||||
|
||||
} // namespace cord_internal
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
|
||||
#endif // ABSL_STRINGS_INTERNAL_CORDZ_HANDLE_H_
|
||||
417
Pods/abseil/absl/strings/internal/cordz_info.cc
generated
Normal file
417
Pods/abseil/absl/strings/internal/cordz_info.cc
generated
Normal file
@@ -0,0 +1,417 @@
|
||||
// Copyright 2019 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/cordz_info.h"
|
||||
|
||||
#include "absl/base/config.h"
|
||||
#include "absl/base/internal/spinlock.h"
|
||||
#include "absl/container/inlined_vector.h"
|
||||
#include "absl/debugging/stacktrace.h"
|
||||
#include "absl/strings/internal/cord_internal.h"
|
||||
#include "absl/strings/internal/cord_rep_btree.h"
|
||||
#include "absl/strings/internal/cord_rep_crc.h"
|
||||
#include "absl/strings/internal/cordz_handle.h"
|
||||
#include "absl/strings/internal/cordz_statistics.h"
|
||||
#include "absl/strings/internal/cordz_update_tracker.h"
|
||||
#include "absl/synchronization/mutex.h"
|
||||
#include "absl/time/clock.h"
|
||||
#include "absl/types/span.h"
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace cord_internal {
|
||||
|
||||
#ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL
|
||||
constexpr size_t CordzInfo::kMaxStackDepth;
|
||||
#endif
|
||||
|
||||
ABSL_CONST_INIT CordzInfo::List CordzInfo::global_list_{absl::kConstInit};
|
||||
|
||||
namespace {
|
||||
|
||||
// CordRepAnalyzer performs the analysis of a cord.
|
||||
//
|
||||
// It computes absolute node counts and total memory usage, and an 'estimated
|
||||
// fair share memory usage` statistic.
|
||||
// Conceptually, it divides the 'memory usage' at each location in the 'cord
|
||||
// graph' by the cumulative reference count of that location. The cumulative
|
||||
// reference count is the factored total of all edges leading into that node.
|
||||
//
|
||||
// The top level node is treated specially: we assume the current thread
|
||||
// (typically called from the CordzHandler) to hold a reference purely to
|
||||
// perform a safe analysis, and not being part of the application. So we
|
||||
// subtract 1 from the reference count of the top node to compute the
|
||||
// 'application fair share' excluding the reference of the current thread.
|
||||
//
|
||||
// An example of fair sharing, and why we multiply reference counts:
|
||||
// Assume we have 2 CordReps, both being a Substring referencing a Flat:
|
||||
// CordSubstring A (refcount = 5) --> child Flat C (refcount = 2)
|
||||
// CordSubstring B (refcount = 9) --> child Flat C (refcount = 2)
|
||||
//
|
||||
// Flat C has 2 incoming edges from the 2 substrings (refcount = 2) and is not
|
||||
// referenced directly anywhere else. Translated into a 'fair share', we then
|
||||
// attribute 50% of the memory (memory / refcount = 2) to each incoming edge.
|
||||
// Rep A has a refcount of 5, so we attribute each incoming edge 1 / 5th of the
|
||||
// memory cost below it, i.e.: the fair share of Rep A of the memory used by C
|
||||
// is then 'memory C / (refcount C * refcount A) + (memory A / refcount A)'.
|
||||
// It is also easy to see how all incoming edges add up to 100%.
|
||||
class CordRepAnalyzer {
|
||||
public:
|
||||
// Creates an analyzer instance binding to `statistics`.
|
||||
explicit CordRepAnalyzer(CordzStatistics& statistics)
|
||||
: statistics_(statistics) {}
|
||||
|
||||
// Analyzes the memory statistics and node counts for the provided `rep`, and
|
||||
// adds the results to `statistics`. Note that node counts and memory sizes
|
||||
// are not initialized, computed values are added to any existing values.
|
||||
void AnalyzeCordRep(const CordRep* rep) {
|
||||
ABSL_ASSERT(rep != nullptr);
|
||||
|
||||
// Process all linear nodes.
|
||||
// As per the class comments, use refcout - 1 on the top level node, as the
|
||||
// top level node is assumed to be referenced only for analysis purposes.
|
||||
size_t refcount = rep->refcount.Get();
|
||||
RepRef repref{rep, (refcount > 1) ? refcount - 1 : 1};
|
||||
|
||||
// Process the top level CRC node, if present.
|
||||
if (repref.tag() == CRC) {
|
||||
statistics_.node_count++;
|
||||
statistics_.node_counts.crc++;
|
||||
memory_usage_.Add(sizeof(CordRepCrc), repref.refcount);
|
||||
repref = repref.Child(repref.rep->crc()->child);
|
||||
}
|
||||
|
||||
// Process all top level linear nodes (substrings and flats).
|
||||
repref = CountLinearReps(repref, memory_usage_);
|
||||
|
||||
switch (repref.tag()) {
|
||||
case CordRepKind::BTREE:
|
||||
AnalyzeBtree(repref);
|
||||
break;
|
||||
default:
|
||||
// We should have a btree node if not null.
|
||||
ABSL_ASSERT(repref.tag() == CordRepKind::UNUSED_0);
|
||||
break;
|
||||
}
|
||||
|
||||
// Adds values to output
|
||||
statistics_.estimated_memory_usage += memory_usage_.total;
|
||||
statistics_.estimated_fair_share_memory_usage +=
|
||||
static_cast<size_t>(memory_usage_.fair_share);
|
||||
}
|
||||
|
||||
private:
|
||||
// RepRef identifies a CordRep* inside the Cord tree with its cumulative
|
||||
// refcount including itself. For example, a tree consisting of a substring
|
||||
// with a refcount of 3 and a child flat with a refcount of 4 will have RepRef
|
||||
// refcounts of 3 and 12 respectively.
|
||||
struct RepRef {
|
||||
const CordRep* rep;
|
||||
size_t refcount;
|
||||
|
||||
// Returns a 'child' RepRef which contains the cumulative reference count
|
||||
// of this instance multiplied by the child's reference count. Returns a
|
||||
// nullptr RepRef value with a refcount of 0 if `child` is nullptr.
|
||||
RepRef Child(const CordRep* child) const {
|
||||
if (child == nullptr) return RepRef{nullptr, 0};
|
||||
return RepRef{child, refcount * child->refcount.Get()};
|
||||
}
|
||||
|
||||
// Returns the tag of this rep, or UNUSED_0 if this instance is null
|
||||
constexpr CordRepKind tag() const {
|
||||
ABSL_ASSERT(rep == nullptr || rep->tag != CordRepKind::UNUSED_0);
|
||||
return rep ? static_cast<CordRepKind>(rep->tag) : CordRepKind::UNUSED_0;
|
||||
}
|
||||
};
|
||||
|
||||
// Memory usage values
|
||||
struct MemoryUsage {
|
||||
size_t total = 0;
|
||||
double fair_share = 0.0;
|
||||
|
||||
// Adds 'size` memory usage to this class, with a cumulative (recursive)
|
||||
// reference count of `refcount`
|
||||
void Add(size_t size, size_t refcount) {
|
||||
total += size;
|
||||
fair_share += static_cast<double>(size) / refcount;
|
||||
}
|
||||
};
|
||||
|
||||
// Counts a flat of the provide allocated size
|
||||
void CountFlat(size_t size) {
|
||||
statistics_.node_count++;
|
||||
statistics_.node_counts.flat++;
|
||||
if (size <= 64) {
|
||||
statistics_.node_counts.flat_64++;
|
||||
} else if (size <= 128) {
|
||||
statistics_.node_counts.flat_128++;
|
||||
} else if (size <= 256) {
|
||||
statistics_.node_counts.flat_256++;
|
||||
} else if (size <= 512) {
|
||||
statistics_.node_counts.flat_512++;
|
||||
} else if (size <= 1024) {
|
||||
statistics_.node_counts.flat_1k++;
|
||||
}
|
||||
}
|
||||
|
||||
// Processes 'linear' reps (substring, flat, external) not requiring iteration
|
||||
// or recursion. Returns RefRep{null} if all reps were processed, else returns
|
||||
// the top-most non-linear concat or ring cordrep.
|
||||
// Node counts are updated into `statistics_`, memory usage is update into
|
||||
// `memory_usage`, which typically references `memory_usage_` except for ring
|
||||
// buffers where we count children unrounded.
|
||||
RepRef CountLinearReps(RepRef rep, MemoryUsage& memory_usage) {
|
||||
// Consume all substrings
|
||||
while (rep.tag() == SUBSTRING) {
|
||||
statistics_.node_count++;
|
||||
statistics_.node_counts.substring++;
|
||||
memory_usage.Add(sizeof(CordRepSubstring), rep.refcount);
|
||||
rep = rep.Child(rep.rep->substring()->child);
|
||||
}
|
||||
|
||||
// Consume possible FLAT
|
||||
if (rep.tag() >= FLAT) {
|
||||
size_t size = rep.rep->flat()->AllocatedSize();
|
||||
CountFlat(size);
|
||||
memory_usage.Add(size, rep.refcount);
|
||||
return RepRef{nullptr, 0};
|
||||
}
|
||||
|
||||
// Consume possible external
|
||||
if (rep.tag() == EXTERNAL) {
|
||||
statistics_.node_count++;
|
||||
statistics_.node_counts.external++;
|
||||
size_t size = rep.rep->length + sizeof(CordRepExternalImpl<intptr_t>);
|
||||
memory_usage.Add(size, rep.refcount);
|
||||
return RepRef{nullptr, 0};
|
||||
}
|
||||
|
||||
return rep;
|
||||
}
|
||||
|
||||
// Analyzes the provided btree.
|
||||
void AnalyzeBtree(RepRef rep) {
|
||||
statistics_.node_count++;
|
||||
statistics_.node_counts.btree++;
|
||||
memory_usage_.Add(sizeof(CordRepBtree), rep.refcount);
|
||||
const CordRepBtree* tree = rep.rep->btree();
|
||||
if (tree->height() > 0) {
|
||||
for (CordRep* edge : tree->Edges()) {
|
||||
AnalyzeBtree(rep.Child(edge));
|
||||
}
|
||||
} else {
|
||||
for (CordRep* edge : tree->Edges()) {
|
||||
CountLinearReps(rep.Child(edge), memory_usage_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CordzStatistics& statistics_;
|
||||
MemoryUsage memory_usage_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
CordzInfo* CordzInfo::Head(const CordzSnapshot& snapshot) {
|
||||
ABSL_ASSERT(snapshot.is_snapshot());
|
||||
|
||||
// We can do an 'unsafe' load of 'head', as we are guaranteed that the
|
||||
// instance it points to is kept alive by the provided CordzSnapshot, so we
|
||||
// can simply return the current value using an acquire load.
|
||||
// We do enforce in DEBUG builds that the 'head' value is present in the
|
||||
// delete queue: ODR violations may lead to 'snapshot' and 'global_list_'
|
||||
// being in different libraries / modules.
|
||||
CordzInfo* head = global_list_.head.load(std::memory_order_acquire);
|
||||
ABSL_ASSERT(snapshot.DiagnosticsHandleIsSafeToInspect(head));
|
||||
return head;
|
||||
}
|
||||
|
||||
CordzInfo* CordzInfo::Next(const CordzSnapshot& snapshot) const {
|
||||
ABSL_ASSERT(snapshot.is_snapshot());
|
||||
|
||||
// Similar to the 'Head()' function, we do not need a mutex here.
|
||||
CordzInfo* next = ci_next_.load(std::memory_order_acquire);
|
||||
ABSL_ASSERT(snapshot.DiagnosticsHandleIsSafeToInspect(this));
|
||||
ABSL_ASSERT(snapshot.DiagnosticsHandleIsSafeToInspect(next));
|
||||
return next;
|
||||
}
|
||||
|
||||
void CordzInfo::TrackCord(InlineData& cord, MethodIdentifier method) {
|
||||
assert(cord.is_tree());
|
||||
assert(!cord.is_profiled());
|
||||
CordzInfo* cordz_info = new CordzInfo(cord.as_tree(), nullptr, method);
|
||||
cord.set_cordz_info(cordz_info);
|
||||
cordz_info->Track();
|
||||
}
|
||||
|
||||
void CordzInfo::TrackCord(InlineData& cord, const InlineData& src,
|
||||
MethodIdentifier method) {
|
||||
assert(cord.is_tree());
|
||||
assert(src.is_tree());
|
||||
|
||||
// Unsample current as we the current cord is being replaced with 'src',
|
||||
// so any method history is no longer relevant.
|
||||
CordzInfo* cordz_info = cord.cordz_info();
|
||||
if (cordz_info != nullptr) cordz_info->Untrack();
|
||||
|
||||
// Start new cord sample
|
||||
cordz_info = new CordzInfo(cord.as_tree(), src.cordz_info(), method);
|
||||
cord.set_cordz_info(cordz_info);
|
||||
cordz_info->Track();
|
||||
}
|
||||
|
||||
void CordzInfo::MaybeTrackCordImpl(InlineData& cord, const InlineData& src,
|
||||
MethodIdentifier method) {
|
||||
if (src.is_profiled()) {
|
||||
TrackCord(cord, src, method);
|
||||
} else if (cord.is_profiled()) {
|
||||
cord.cordz_info()->Untrack();
|
||||
cord.clear_cordz_info();
|
||||
}
|
||||
}
|
||||
|
||||
CordzInfo::MethodIdentifier CordzInfo::GetParentMethod(const CordzInfo* src) {
|
||||
if (src == nullptr) return MethodIdentifier::kUnknown;
|
||||
return src->parent_method_ != MethodIdentifier::kUnknown ? src->parent_method_
|
||||
: src->method_;
|
||||
}
|
||||
|
||||
size_t CordzInfo::FillParentStack(const CordzInfo* src, void** stack) {
|
||||
assert(stack);
|
||||
if (src == nullptr) return 0;
|
||||
if (src->parent_stack_depth_) {
|
||||
memcpy(stack, src->parent_stack_, src->parent_stack_depth_ * sizeof(void*));
|
||||
return src->parent_stack_depth_;
|
||||
}
|
||||
memcpy(stack, src->stack_, src->stack_depth_ * sizeof(void*));
|
||||
return src->stack_depth_;
|
||||
}
|
||||
|
||||
CordzInfo::CordzInfo(CordRep* rep,
|
||||
const CordzInfo* src,
|
||||
MethodIdentifier method)
|
||||
: rep_(rep),
|
||||
stack_depth_(
|
||||
static_cast<size_t>(absl::GetStackTrace(stack_,
|
||||
/*max_depth=*/kMaxStackDepth,
|
||||
/*skip_count=*/1))),
|
||||
parent_stack_depth_(FillParentStack(src, parent_stack_)),
|
||||
method_(method),
|
||||
parent_method_(GetParentMethod(src)),
|
||||
create_time_(absl::Now()) {
|
||||
update_tracker_.LossyAdd(method);
|
||||
if (src) {
|
||||
// Copy parent counters.
|
||||
update_tracker_.LossyAdd(src->update_tracker_);
|
||||
}
|
||||
}
|
||||
|
||||
CordzInfo::~CordzInfo() {
|
||||
// `rep_` is potentially kept alive if CordzInfo is included
|
||||
// in a collection snapshot (which should be rare).
|
||||
if (ABSL_PREDICT_FALSE(rep_)) {
|
||||
CordRep::Unref(rep_);
|
||||
}
|
||||
}
|
||||
|
||||
void CordzInfo::Track() {
|
||||
SpinLockHolder l(&list_->mutex);
|
||||
|
||||
CordzInfo* const head = list_->head.load(std::memory_order_acquire);
|
||||
if (head != nullptr) {
|
||||
head->ci_prev_.store(this, std::memory_order_release);
|
||||
}
|
||||
ci_next_.store(head, std::memory_order_release);
|
||||
list_->head.store(this, std::memory_order_release);
|
||||
}
|
||||
|
||||
void CordzInfo::Untrack() {
|
||||
ODRCheck();
|
||||
{
|
||||
SpinLockHolder l(&list_->mutex);
|
||||
|
||||
CordzInfo* const head = list_->head.load(std::memory_order_acquire);
|
||||
CordzInfo* const next = ci_next_.load(std::memory_order_acquire);
|
||||
CordzInfo* const prev = ci_prev_.load(std::memory_order_acquire);
|
||||
|
||||
if (next) {
|
||||
ABSL_ASSERT(next->ci_prev_.load(std::memory_order_acquire) == this);
|
||||
next->ci_prev_.store(prev, std::memory_order_release);
|
||||
}
|
||||
if (prev) {
|
||||
ABSL_ASSERT(head != this);
|
||||
ABSL_ASSERT(prev->ci_next_.load(std::memory_order_acquire) == this);
|
||||
prev->ci_next_.store(next, std::memory_order_release);
|
||||
} else {
|
||||
ABSL_ASSERT(head == this);
|
||||
list_->head.store(next, std::memory_order_release);
|
||||
}
|
||||
}
|
||||
|
||||
// We can no longer be discovered: perform a fast path check if we are not
|
||||
// listed on any delete queue, so we can directly delete this instance.
|
||||
if (SafeToDelete()) {
|
||||
UnsafeSetCordRep(nullptr);
|
||||
delete this;
|
||||
return;
|
||||
}
|
||||
|
||||
// We are likely part of a snapshot, extend the life of the CordRep
|
||||
{
|
||||
absl::MutexLock lock(&mutex_);
|
||||
if (rep_) CordRep::Ref(rep_);
|
||||
}
|
||||
CordzHandle::Delete(this);
|
||||
}
|
||||
|
||||
void CordzInfo::Lock(MethodIdentifier method)
|
||||
ABSL_EXCLUSIVE_LOCK_FUNCTION(mutex_) {
|
||||
mutex_.Lock();
|
||||
update_tracker_.LossyAdd(method);
|
||||
assert(rep_);
|
||||
}
|
||||
|
||||
void CordzInfo::Unlock() ABSL_UNLOCK_FUNCTION(mutex_) {
|
||||
bool tracked = rep_ != nullptr;
|
||||
mutex_.Unlock();
|
||||
if (!tracked) {
|
||||
Untrack();
|
||||
}
|
||||
}
|
||||
|
||||
absl::Span<void* const> CordzInfo::GetStack() const {
|
||||
return absl::MakeConstSpan(stack_, stack_depth_);
|
||||
}
|
||||
|
||||
absl::Span<void* const> CordzInfo::GetParentStack() const {
|
||||
return absl::MakeConstSpan(parent_stack_, parent_stack_depth_);
|
||||
}
|
||||
|
||||
CordzStatistics CordzInfo::GetCordzStatistics() const {
|
||||
CordzStatistics stats;
|
||||
stats.method = method_;
|
||||
stats.parent_method = parent_method_;
|
||||
stats.update_tracker = update_tracker_;
|
||||
if (CordRep* rep = RefCordRep()) {
|
||||
stats.size = rep->length;
|
||||
CordRepAnalyzer analyzer(stats);
|
||||
analyzer.AnalyzeCordRep(rep);
|
||||
CordRep::Unref(rep);
|
||||
}
|
||||
return stats;
|
||||
}
|
||||
|
||||
} // namespace cord_internal
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
298
Pods/abseil/absl/strings/internal/cordz_info.h
generated
Normal file
298
Pods/abseil/absl/strings/internal/cordz_info.h
generated
Normal file
@@ -0,0 +1,298 @@
|
||||
// Copyright 2019 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_CORDZ_INFO_H_
|
||||
#define ABSL_STRINGS_INTERNAL_CORDZ_INFO_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
|
||||
#include "absl/base/config.h"
|
||||
#include "absl/base/internal/raw_logging.h"
|
||||
#include "absl/base/internal/spinlock.h"
|
||||
#include "absl/base/thread_annotations.h"
|
||||
#include "absl/strings/internal/cord_internal.h"
|
||||
#include "absl/strings/internal/cordz_functions.h"
|
||||
#include "absl/strings/internal/cordz_handle.h"
|
||||
#include "absl/strings/internal/cordz_statistics.h"
|
||||
#include "absl/strings/internal/cordz_update_tracker.h"
|
||||
#include "absl/synchronization/mutex.h"
|
||||
#include "absl/types/span.h"
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace cord_internal {
|
||||
|
||||
// CordzInfo tracks a profiled Cord. Each of these objects can be in two places.
|
||||
// If a Cord is alive, the CordzInfo will be in the global_cordz_infos map, and
|
||||
// can also be retrieved via the linked list starting with
|
||||
// global_cordz_infos_head and continued via the cordz_info_next() method. When
|
||||
// a Cord has reached the end of its lifespan, the CordzInfo object will be
|
||||
// migrated out of the global_cordz_infos list and the global_cordz_infos_map,
|
||||
// and will either be deleted or appended to the global_delete_queue. If it is
|
||||
// placed on the global_delete_queue, the CordzInfo object will be cleaned in
|
||||
// the destructor of a CordzSampleToken object.
|
||||
class ABSL_LOCKABLE CordzInfo : public CordzHandle {
|
||||
public:
|
||||
using MethodIdentifier = CordzUpdateTracker::MethodIdentifier;
|
||||
|
||||
// TrackCord creates a CordzInfo instance which tracks important metrics of
|
||||
// a sampled cord, and stores the created CordzInfo instance into `cord'. All
|
||||
// CordzInfo instances are placed in a global list which is used to discover
|
||||
// and snapshot all actively tracked cords. Callers are responsible for
|
||||
// calling UntrackCord() before the tracked Cord instance is deleted, or to
|
||||
// stop tracking the sampled Cord. Callers are also responsible for guarding
|
||||
// changes to the 'tree' value of a Cord (InlineData.tree) through the Lock()
|
||||
// and Unlock() calls. Any change resulting in a new tree value for the cord
|
||||
// requires a call to SetCordRep() before the old tree has been unreffed
|
||||
// and/or deleted. `method` identifies the Cord public API method initiating
|
||||
// the cord to be sampled.
|
||||
// Requires `cord` to hold a tree, and `cord.cordz_info()` to be null.
|
||||
static void TrackCord(InlineData& cord, MethodIdentifier method);
|
||||
|
||||
// Identical to TrackCord(), except that this function fills the
|
||||
// `parent_stack` and `parent_method` properties of the returned CordzInfo
|
||||
// instance from the provided `src` instance if `src` is sampled.
|
||||
// This function should be used for sampling 'copy constructed' and 'copy
|
||||
// assigned' cords. This function allows 'cord` to be already sampled, in
|
||||
// which case the CordzInfo will be newly created from `src`.
|
||||
static void TrackCord(InlineData& cord, const InlineData& src,
|
||||
MethodIdentifier method);
|
||||
|
||||
// Maybe sample the cord identified by 'cord' for method 'method'.
|
||||
// Uses `cordz_should_profile` to randomly pick cords to be sampled, and if
|
||||
// so, invokes `TrackCord` to start sampling `cord`.
|
||||
static void MaybeTrackCord(InlineData& cord, MethodIdentifier method);
|
||||
|
||||
// Maybe sample the cord identified by 'cord' for method 'method'.
|
||||
// `src` identifies a 'parent' cord which is assigned to `cord`, typically the
|
||||
// input cord for a copy constructor, or an assign method such as `operator=`
|
||||
// `cord` will be sampled if (and only if) `src` is sampled.
|
||||
// If `cord` is currently being sampled and `src` is not being sampled, then
|
||||
// this function will stop sampling the cord and reset the cord's cordz_info.
|
||||
//
|
||||
// Previously this function defined that `cord` will be sampled if either
|
||||
// `src` is sampled, or if `cord` is randomly picked for sampling. However,
|
||||
// this can cause issues, as there may be paths where some cord is assigned an
|
||||
// indirect copy of it's own value. As such a 'string of copies' would then
|
||||
// remain sampled (`src.is_profiled`), then assigning such a cord back to
|
||||
// 'itself' creates a cycle where the cord will converge to 'always sampled`.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// Cord x;
|
||||
// for (...) {
|
||||
// // Copy ctor --> y.is_profiled := x.is_profiled | random(...)
|
||||
// Cord y = x;
|
||||
// ...
|
||||
// // Assign x = y --> x.is_profiled = y.is_profiled | random(...)
|
||||
// // ==> x.is_profiled |= random(...)
|
||||
// // ==> x converges to 'always profiled'
|
||||
// x = y;
|
||||
// }
|
||||
static void MaybeTrackCord(InlineData& cord, const InlineData& src,
|
||||
MethodIdentifier method);
|
||||
|
||||
// Stops tracking changes for a sampled cord, and deletes the provided info.
|
||||
// This function must be called before the sampled cord instance is deleted,
|
||||
// and before the root cordrep of the sampled cord is unreffed.
|
||||
// This function may extend the lifetime of the cordrep in cases where the
|
||||
// CordInfo instance is being held by a concurrent collection thread.
|
||||
void Untrack();
|
||||
|
||||
// Invokes UntrackCord() on `info` if `info` is not null.
|
||||
static void MaybeUntrackCord(CordzInfo* info);
|
||||
|
||||
CordzInfo() = delete;
|
||||
CordzInfo(const CordzInfo&) = delete;
|
||||
CordzInfo& operator=(const CordzInfo&) = delete;
|
||||
|
||||
// Retrieves the oldest existing CordzInfo.
|
||||
static CordzInfo* Head(const CordzSnapshot& snapshot)
|
||||
ABSL_NO_THREAD_SAFETY_ANALYSIS;
|
||||
|
||||
// Retrieves the next oldest existing CordzInfo older than 'this' instance.
|
||||
CordzInfo* Next(const CordzSnapshot& snapshot) const
|
||||
ABSL_NO_THREAD_SAFETY_ANALYSIS;
|
||||
|
||||
// Locks this instance for the update identified by `method`.
|
||||
// Increases the count for `method` in `update_tracker`.
|
||||
void Lock(MethodIdentifier method) ABSL_EXCLUSIVE_LOCK_FUNCTION(mutex_);
|
||||
|
||||
// Unlocks this instance. If the contained `rep` has been set to null
|
||||
// indicating the Cord has been cleared or is otherwise no longer sampled,
|
||||
// then this method will delete this CordzInfo instance.
|
||||
void Unlock() ABSL_UNLOCK_FUNCTION(mutex_);
|
||||
|
||||
// Asserts that this CordzInfo instance is locked.
|
||||
void AssertHeld() ABSL_ASSERT_EXCLUSIVE_LOCK(mutex_);
|
||||
|
||||
// Updates the `rep` property of this instance. This methods is invoked by
|
||||
// Cord logic each time the root node of a sampled Cord changes, and before
|
||||
// the old root reference count is deleted. This guarantees that collection
|
||||
// code can always safely take a reference on the tracked cord.
|
||||
// Requires a lock to be held through the `Lock()` method.
|
||||
// TODO(b/117940323): annotate with ABSL_EXCLUSIVE_LOCKS_REQUIRED once all
|
||||
// Cord code is in a state where this can be proven true by the compiler.
|
||||
void SetCordRep(CordRep* rep);
|
||||
|
||||
// Returns the current `rep` property of this instance with a reference
|
||||
// added, or null if this instance represents a cord that has since been
|
||||
// deleted or untracked.
|
||||
CordRep* RefCordRep() const ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Returns the current value of `rep_` for testing purposes only.
|
||||
CordRep* GetCordRepForTesting() const ABSL_NO_THREAD_SAFETY_ANALYSIS {
|
||||
return rep_;
|
||||
}
|
||||
|
||||
// Sets the current value of `rep_` for testing purposes only.
|
||||
void SetCordRepForTesting(CordRep* rep) ABSL_NO_THREAD_SAFETY_ANALYSIS {
|
||||
rep_ = rep;
|
||||
}
|
||||
|
||||
// Returns the stack trace for where the cord was first sampled. Cords are
|
||||
// potentially sampled when they promote from an inlined cord to a tree or
|
||||
// ring representation, which is not necessarily the location where the cord
|
||||
// was first created. Some cords are created as inlined cords, and only as
|
||||
// data is added do they become a non-inlined cord. However, typically the
|
||||
// location represents reasonably well where the cord is 'created'.
|
||||
absl::Span<void* const> GetStack() const;
|
||||
|
||||
// Returns the stack trace for a sampled cord's 'parent stack trace'. This
|
||||
// value may be set if the cord is sampled (promoted) after being created
|
||||
// from, or being assigned the value of an existing (sampled) cord.
|
||||
absl::Span<void* const> GetParentStack() const;
|
||||
|
||||
// Retrieves the CordzStatistics associated with this Cord. The statistics
|
||||
// are only updated when a Cord goes through a mutation, such as an Append
|
||||
// or RemovePrefix.
|
||||
CordzStatistics GetCordzStatistics() const;
|
||||
|
||||
private:
|
||||
using SpinLock = absl::base_internal::SpinLock;
|
||||
using SpinLockHolder = ::absl::base_internal::SpinLockHolder;
|
||||
|
||||
// Global cordz info list. CordzInfo stores a pointer to the global list
|
||||
// instance to harden against ODR violations.
|
||||
struct List {
|
||||
constexpr explicit List(absl::ConstInitType)
|
||||
: mutex(absl::kConstInit,
|
||||
absl::base_internal::SCHEDULE_COOPERATIVE_AND_KERNEL) {}
|
||||
|
||||
SpinLock mutex;
|
||||
std::atomic<CordzInfo*> head ABSL_GUARDED_BY(mutex){nullptr};
|
||||
};
|
||||
|
||||
static constexpr size_t kMaxStackDepth = 64;
|
||||
|
||||
explicit CordzInfo(CordRep* rep, const CordzInfo* src,
|
||||
MethodIdentifier method);
|
||||
~CordzInfo() override;
|
||||
|
||||
// Sets `rep_` without holding a lock.
|
||||
void UnsafeSetCordRep(CordRep* rep) ABSL_NO_THREAD_SAFETY_ANALYSIS;
|
||||
|
||||
void Track();
|
||||
|
||||
// Returns the parent method from `src`, which is either `parent_method_` or
|
||||
// `method_` depending on `parent_method_` being kUnknown.
|
||||
// Returns kUnknown if `src` is null.
|
||||
static MethodIdentifier GetParentMethod(const CordzInfo* src);
|
||||
|
||||
// Fills the provided stack from `src`, copying either `parent_stack_` or
|
||||
// `stack_` depending on `parent_stack_` being empty, returning the size of
|
||||
// the parent stack.
|
||||
// Returns 0 if `src` is null.
|
||||
static size_t FillParentStack(const CordzInfo* src, void** stack);
|
||||
|
||||
void ODRCheck() const {
|
||||
#ifndef NDEBUG
|
||||
ABSL_RAW_CHECK(list_ == &global_list_, "ODR violation in Cord");
|
||||
#endif
|
||||
}
|
||||
|
||||
// Non-inlined implementation of `MaybeTrackCord`, which is executed if
|
||||
// either `src` is sampled or `cord` is sampled, and either untracks or
|
||||
// tracks `cord` as documented per `MaybeTrackCord`.
|
||||
static void MaybeTrackCordImpl(InlineData& cord, const InlineData& src,
|
||||
MethodIdentifier method);
|
||||
|
||||
ABSL_CONST_INIT static List global_list_;
|
||||
List* const list_ = &global_list_;
|
||||
|
||||
// ci_prev_ and ci_next_ require the global list mutex to be held.
|
||||
// Unfortunately we can't use thread annotations such that the thread safety
|
||||
// analysis understands that list_ and global_list_ are one and the same.
|
||||
std::atomic<CordzInfo*> ci_prev_{nullptr};
|
||||
std::atomic<CordzInfo*> ci_next_{nullptr};
|
||||
|
||||
mutable absl::Mutex mutex_;
|
||||
CordRep* rep_ ABSL_GUARDED_BY(mutex_);
|
||||
|
||||
void* stack_[kMaxStackDepth];
|
||||
void* parent_stack_[kMaxStackDepth];
|
||||
const size_t stack_depth_;
|
||||
const size_t parent_stack_depth_;
|
||||
const MethodIdentifier method_;
|
||||
const MethodIdentifier parent_method_;
|
||||
CordzUpdateTracker update_tracker_;
|
||||
const absl::Time create_time_;
|
||||
};
|
||||
|
||||
inline ABSL_ATTRIBUTE_ALWAYS_INLINE void CordzInfo::MaybeTrackCord(
|
||||
InlineData& cord, MethodIdentifier method) {
|
||||
if (ABSL_PREDICT_FALSE(cordz_should_profile())) {
|
||||
TrackCord(cord, method);
|
||||
}
|
||||
}
|
||||
|
||||
inline ABSL_ATTRIBUTE_ALWAYS_INLINE void CordzInfo::MaybeTrackCord(
|
||||
InlineData& cord, const InlineData& src, MethodIdentifier method) {
|
||||
if (ABSL_PREDICT_FALSE(InlineData::is_either_profiled(cord, src))) {
|
||||
MaybeTrackCordImpl(cord, src, method);
|
||||
}
|
||||
}
|
||||
|
||||
inline ABSL_ATTRIBUTE_ALWAYS_INLINE void CordzInfo::MaybeUntrackCord(
|
||||
CordzInfo* info) {
|
||||
if (ABSL_PREDICT_FALSE(info)) {
|
||||
info->Untrack();
|
||||
}
|
||||
}
|
||||
|
||||
inline void CordzInfo::AssertHeld() ABSL_ASSERT_EXCLUSIVE_LOCK(mutex_) {
|
||||
#ifndef NDEBUG
|
||||
mutex_.AssertHeld();
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void CordzInfo::SetCordRep(CordRep* rep) {
|
||||
AssertHeld();
|
||||
rep_ = rep;
|
||||
}
|
||||
|
||||
inline void CordzInfo::UnsafeSetCordRep(CordRep* rep) { rep_ = rep; }
|
||||
|
||||
inline CordRep* CordzInfo::RefCordRep() const ABSL_LOCKS_EXCLUDED(mutex_) {
|
||||
MutexLock lock(&mutex_);
|
||||
return rep_ ? CordRep::Ref(rep_) : nullptr;
|
||||
}
|
||||
|
||||
} // namespace cord_internal
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
|
||||
#endif // ABSL_STRINGS_INTERNAL_CORDZ_INFO_H_
|
||||
88
Pods/abseil/absl/strings/internal/cordz_statistics.h
generated
Normal file
88
Pods/abseil/absl/strings/internal/cordz_statistics.h
generated
Normal file
@@ -0,0 +1,88 @@
|
||||
// Copyright 2019 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_CORDZ_STATISTICS_H_
|
||||
#define ABSL_STRINGS_INTERNAL_CORDZ_STATISTICS_H_
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "absl/base/config.h"
|
||||
#include "absl/strings/internal/cordz_update_tracker.h"
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace cord_internal {
|
||||
|
||||
// CordzStatistics captures some meta information about a Cord's shape.
|
||||
struct CordzStatistics {
|
||||
using MethodIdentifier = CordzUpdateTracker::MethodIdentifier;
|
||||
|
||||
// Node counts information
|
||||
struct NodeCounts {
|
||||
size_t flat = 0; // #flats
|
||||
size_t flat_64 = 0; // #flats up to 64 bytes
|
||||
size_t flat_128 = 0; // #flats up to 128 bytes
|
||||
size_t flat_256 = 0; // #flats up to 256 bytes
|
||||
size_t flat_512 = 0; // #flats up to 512 bytes
|
||||
size_t flat_1k = 0; // #flats up to 1K bytes
|
||||
size_t external = 0; // #external reps
|
||||
size_t substring = 0; // #substring reps
|
||||
size_t concat = 0; // #concat reps
|
||||
size_t ring = 0; // #ring buffer reps
|
||||
size_t btree = 0; // #btree reps
|
||||
size_t crc = 0; // #crc reps
|
||||
};
|
||||
|
||||
// The size of the cord in bytes. This matches the result of Cord::size().
|
||||
size_t size = 0;
|
||||
|
||||
// The estimated memory used by the sampled cord. This value matches the
|
||||
// value as reported by Cord::EstimatedMemoryUsage().
|
||||
// A value of 0 implies the property has not been recorded.
|
||||
size_t estimated_memory_usage = 0;
|
||||
|
||||
// The effective memory used by the sampled cord, inversely weighted by the
|
||||
// effective indegree of each allocated node. This is a representation of the
|
||||
// fair share of memory usage that should be attributed to the sampled cord.
|
||||
// This value is more useful for cases where one or more nodes are referenced
|
||||
// by multiple Cord instances, and for cases where a Cord includes the same
|
||||
// node multiple times (either directly or indirectly).
|
||||
// A value of 0 implies the property has not been recorded.
|
||||
size_t estimated_fair_share_memory_usage = 0;
|
||||
|
||||
// The total number of nodes referenced by this cord.
|
||||
// For ring buffer Cords, this includes the 'ring buffer' node.
|
||||
// For btree Cords, this includes all 'CordRepBtree' tree nodes as well as all
|
||||
// the substring, flat and external nodes referenced by the tree.
|
||||
// A value of 0 implies the property has not been recorded.
|
||||
size_t node_count = 0;
|
||||
|
||||
// Detailed node counts per type
|
||||
NodeCounts node_counts;
|
||||
|
||||
// The cord method responsible for sampling the cord.
|
||||
MethodIdentifier method = MethodIdentifier::kUnknown;
|
||||
|
||||
// The cord method responsible for sampling the parent cord if applicable.
|
||||
MethodIdentifier parent_method = MethodIdentifier::kUnknown;
|
||||
|
||||
// Update tracker tracking invocation count per cord method.
|
||||
CordzUpdateTracker update_tracker;
|
||||
};
|
||||
|
||||
} // namespace cord_internal
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
|
||||
#endif // ABSL_STRINGS_INTERNAL_CORDZ_STATISTICS_H_
|
||||
71
Pods/abseil/absl/strings/internal/cordz_update_scope.h
generated
Normal file
71
Pods/abseil/absl/strings/internal/cordz_update_scope.h
generated
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright 2021 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_CORDZ_UPDATE_SCOPE_H_
|
||||
#define ABSL_STRINGS_INTERNAL_CORDZ_UPDATE_SCOPE_H_
|
||||
|
||||
#include "absl/base/config.h"
|
||||
#include "absl/base/optimization.h"
|
||||
#include "absl/base/thread_annotations.h"
|
||||
#include "absl/strings/internal/cord_internal.h"
|
||||
#include "absl/strings/internal/cordz_info.h"
|
||||
#include "absl/strings/internal/cordz_update_tracker.h"
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace cord_internal {
|
||||
|
||||
// CordzUpdateScope scopes an update to the provided CordzInfo.
|
||||
// The class invokes `info->Lock(method)` and `info->Unlock()` to guard
|
||||
// cordrep updates. This class does nothing if `info` is null.
|
||||
// See also the 'Lock`, `Unlock` and `SetCordRep` methods in `CordzInfo`.
|
||||
class ABSL_SCOPED_LOCKABLE CordzUpdateScope {
|
||||
public:
|
||||
CordzUpdateScope(CordzInfo* info, CordzUpdateTracker::MethodIdentifier method)
|
||||
ABSL_EXCLUSIVE_LOCK_FUNCTION(info)
|
||||
: info_(info) {
|
||||
if (ABSL_PREDICT_FALSE(info_)) {
|
||||
info->Lock(method);
|
||||
}
|
||||
}
|
||||
|
||||
// CordzUpdateScope can not be copied or assigned to.
|
||||
CordzUpdateScope(CordzUpdateScope&& rhs) = delete;
|
||||
CordzUpdateScope(const CordzUpdateScope&) = delete;
|
||||
CordzUpdateScope& operator=(CordzUpdateScope&& rhs) = delete;
|
||||
CordzUpdateScope& operator=(const CordzUpdateScope&) = delete;
|
||||
|
||||
~CordzUpdateScope() ABSL_UNLOCK_FUNCTION() {
|
||||
if (ABSL_PREDICT_FALSE(info_)) {
|
||||
info_->Unlock();
|
||||
}
|
||||
}
|
||||
|
||||
void SetCordRep(CordRep* rep) const {
|
||||
if (ABSL_PREDICT_FALSE(info_)) {
|
||||
info_->SetCordRep(rep);
|
||||
}
|
||||
}
|
||||
|
||||
CordzInfo* info() const { return info_; }
|
||||
|
||||
private:
|
||||
CordzInfo* info_;
|
||||
};
|
||||
|
||||
} // namespace cord_internal
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
|
||||
#endif // ABSL_STRINGS_INTERNAL_CORDZ_UPDATE_SCOPE_H_
|
||||
123
Pods/abseil/absl/strings/internal/cordz_update_tracker.h
generated
Normal file
123
Pods/abseil/absl/strings/internal/cordz_update_tracker.h
generated
Normal file
@@ -0,0 +1,123 @@
|
||||
// Copyright 2021 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_CORDZ_UPDATE_TRACKER_H_
|
||||
#define ABSL_STRINGS_INTERNAL_CORDZ_UPDATE_TRACKER_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
|
||||
#include "absl/base/config.h"
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace cord_internal {
|
||||
|
||||
// CordzUpdateTracker tracks counters for Cord update methods.
|
||||
//
|
||||
// The purpose of CordzUpdateTracker is to track the number of calls to methods
|
||||
// updating Cord data for sampled cords. The class internally uses 'lossy'
|
||||
// atomic operations: Cord is thread-compatible, so there is no need to
|
||||
// synchronize updates. However, Cordz collection threads may call 'Value()' at
|
||||
// any point, so the class needs to provide thread safe access.
|
||||
//
|
||||
// This class is thread-safe. But as per above comments, all non-const methods
|
||||
// should be used single-threaded only: updates are thread-safe but lossy.
|
||||
class CordzUpdateTracker {
|
||||
public:
|
||||
// Tracked update methods.
|
||||
enum MethodIdentifier {
|
||||
kUnknown,
|
||||
kAppendCord,
|
||||
kAppendCordBuffer,
|
||||
kAppendExternalMemory,
|
||||
kAppendString,
|
||||
kAssignCord,
|
||||
kAssignString,
|
||||
kClear,
|
||||
kConstructorCord,
|
||||
kConstructorString,
|
||||
kCordReader,
|
||||
kFlatten,
|
||||
kGetAppendBuffer,
|
||||
kGetAppendRegion,
|
||||
kMakeCordFromExternal,
|
||||
kMoveAppendCord,
|
||||
kMoveAssignCord,
|
||||
kMovePrependCord,
|
||||
kPrependCord,
|
||||
kPrependCordBuffer,
|
||||
kPrependString,
|
||||
kRemovePrefix,
|
||||
kRemoveSuffix,
|
||||
kSetExpectedChecksum,
|
||||
kSubCord,
|
||||
|
||||
// kNumMethods defines the number of entries: must be the last entry.
|
||||
kNumMethods,
|
||||
};
|
||||
|
||||
// Constructs a new instance. All counters are zero-initialized.
|
||||
constexpr CordzUpdateTracker() noexcept : values_{} {}
|
||||
|
||||
// Copy constructs a new instance.
|
||||
CordzUpdateTracker(const CordzUpdateTracker& rhs) noexcept { *this = rhs; }
|
||||
|
||||
// Assigns the provided value to this instance.
|
||||
CordzUpdateTracker& operator=(const CordzUpdateTracker& rhs) noexcept {
|
||||
for (int i = 0; i < kNumMethods; ++i) {
|
||||
values_[i].store(rhs.values_[i].load(std::memory_order_relaxed),
|
||||
std::memory_order_relaxed);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Returns the value for the specified method.
|
||||
int64_t Value(MethodIdentifier method) const {
|
||||
return values_[method].load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// Increases the value for the specified method by `n`
|
||||
void LossyAdd(MethodIdentifier method, int64_t n = 1) {
|
||||
auto& value = values_[method];
|
||||
value.store(value.load(std::memory_order_relaxed) + n,
|
||||
std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// Adds all the values from `src` to this instance
|
||||
void LossyAdd(const CordzUpdateTracker& src) {
|
||||
for (int i = 0; i < kNumMethods; ++i) {
|
||||
MethodIdentifier method = static_cast<MethodIdentifier>(i);
|
||||
if (int64_t value = src.Value(method)) {
|
||||
LossyAdd(method, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// Until C++20 std::atomic is not constexpr default-constructible, so we need
|
||||
// a wrapper for this class to be constexpr constructible.
|
||||
class Counter : public std::atomic<int64_t> {
|
||||
public:
|
||||
constexpr Counter() noexcept : std::atomic<int64_t>(0) {}
|
||||
};
|
||||
|
||||
Counter values_[kNumMethods];
|
||||
};
|
||||
|
||||
} // namespace cord_internal
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
|
||||
#endif // ABSL_STRINGS_INTERNAL_CORDZ_UPDATE_TRACKER_H_
|
||||
93
Pods/abseil/absl/strings/internal/damerau_levenshtein_distance.cc
generated
Normal file
93
Pods/abseil/absl/strings/internal/damerau_levenshtein_distance.cc
generated
Normal file
@@ -0,0 +1,93 @@
|
||||
// Copyright 2022 The Abseil Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "absl/strings/internal/damerau_levenshtein_distance.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <numeric>
|
||||
|
||||
#include "absl/strings/string_view.h"
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace strings_internal {
|
||||
// Calculate DamerauLevenshtein (adjacent transpositions) distance
|
||||
// between two strings,
|
||||
// https://en.wikipedia.org/wiki/Damerau%E2%80%93Levenshtein_distance. The
|
||||
// algorithm follows the condition that no substring is edited more than once.
|
||||
// While this can reduce is larger distance, it's a) a much simpler algorithm
|
||||
// and b) more realistic for the case that typographic mistakes should be
|
||||
// detected.
|
||||
// When the distance is larger than cutoff, or one of the strings has more
|
||||
// than MAX_SIZE=100 characters, the code returns min(MAX_SIZE, cutoff) + 1.
|
||||
uint8_t CappedDamerauLevenshteinDistance(absl::string_view s1,
|
||||
absl::string_view s2, uint8_t cutoff) {
|
||||
const uint8_t MAX_SIZE = 100;
|
||||
const uint8_t _cutoff = std::min(MAX_SIZE, cutoff);
|
||||
const uint8_t cutoff_plus_1 = static_cast<uint8_t>(_cutoff + 1);
|
||||
|
||||
if (s1.size() > s2.size()) std::swap(s1, s2);
|
||||
if (s1.size() + _cutoff < s2.size() || s2.size() > MAX_SIZE)
|
||||
return cutoff_plus_1;
|
||||
|
||||
if (s1.empty())
|
||||
return static_cast<uint8_t>(s2.size());
|
||||
|
||||
// Lower diagonal bound: y = x - lower_diag
|
||||
const uint8_t lower_diag =
|
||||
_cutoff - static_cast<uint8_t>(s2.size() - s1.size());
|
||||
// Upper diagonal bound: y = x + upper_diag
|
||||
const uint8_t upper_diag = _cutoff;
|
||||
|
||||
// d[i][j] is the number of edits required to convert s1[0, i] to s2[0, j]
|
||||
std::array<std::array<uint8_t, MAX_SIZE + 2>, MAX_SIZE + 2> d;
|
||||
std::iota(d[0].begin(), d[0].begin() + upper_diag + 1, 0);
|
||||
d[0][cutoff_plus_1] = cutoff_plus_1;
|
||||
for (size_t i = 1; i <= s1.size(); ++i) {
|
||||
// Deduce begin of relevant window.
|
||||
size_t j_begin = 1;
|
||||
if (i > lower_diag) {
|
||||
j_begin = i - lower_diag;
|
||||
d[i][j_begin - 1] = cutoff_plus_1;
|
||||
} else {
|
||||
d[i][0] = static_cast<uint8_t>(i);
|
||||
}
|
||||
|
||||
// Deduce end of relevant window.
|
||||
size_t j_end = i + upper_diag;
|
||||
if (j_end > s2.size()) {
|
||||
j_end = s2.size();
|
||||
} else {
|
||||
d[i][j_end + 1] = cutoff_plus_1;
|
||||
}
|
||||
|
||||
for (size_t j = j_begin; j <= j_end; ++j) {
|
||||
const uint8_t deletion_distance = d[i - 1][j] + 1;
|
||||
const uint8_t insertion_distance = d[i][j - 1] + 1;
|
||||
const uint8_t mismatched_tail_cost = s1[i - 1] == s2[j - 1] ? 0 : 1;
|
||||
const uint8_t mismatch_distance = d[i - 1][j - 1] + mismatched_tail_cost;
|
||||
uint8_t transposition_distance = _cutoff + 1;
|
||||
if (i > 1 && j > 1 && s1[i - 1] == s2[j - 2] && s1[i - 2] == s2[j - 1])
|
||||
transposition_distance = d[i - 2][j - 2] + 1;
|
||||
d[i][j] = std::min({cutoff_plus_1, deletion_distance, insertion_distance,
|
||||
mismatch_distance, transposition_distance});
|
||||
}
|
||||
}
|
||||
return d[s1.size()][s2.size()];
|
||||
}
|
||||
|
||||
} // namespace strings_internal
|
||||
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
34
Pods/abseil/absl/strings/internal/damerau_levenshtein_distance.h
generated
Normal file
34
Pods/abseil/absl/strings/internal/damerau_levenshtein_distance.h
generated
Normal file
@@ -0,0 +1,34 @@
|
||||
// Copyright 2022 The Abseil Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef ABSL_STRINGS_INTERNAL_DAMERAU_LEVENSHTEIN_DISTANCE_H_
|
||||
#define ABSL_STRINGS_INTERNAL_DAMERAU_LEVENSHTEIN_DISTANCE_H_
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "absl/strings/string_view.h"
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace strings_internal {
|
||||
// Calculate DamerauLevenshtein distance between two strings.
|
||||
// When the distance is larger than cutoff, the code just returns cutoff + 1.
|
||||
uint8_t CappedDamerauLevenshteinDistance(absl::string_view s1,
|
||||
absl::string_view s2, uint8_t cutoff);
|
||||
|
||||
} // namespace strings_internal
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
|
||||
#endif // ABSL_STRINGS_INTERNAL_DAMERAU_LEVENSHTEIN_DISTANCE_H_
|
||||
206
Pods/abseil/absl/strings/internal/escaping.cc
generated
Normal file
206
Pods/abseil/absl/strings/internal/escaping.cc
generated
Normal file
@@ -0,0 +1,206 @@
|
||||
// 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/escaping.h"
|
||||
|
||||
#include "absl/base/internal/endian.h"
|
||||
#include "absl/base/internal/raw_logging.h"
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace strings_internal {
|
||||
|
||||
// The two strings below provide maps from normal 6-bit characters to their
|
||||
// base64-escaped equivalent.
|
||||
// For the inverse case, see kUn(WebSafe)Base64 in the external
|
||||
// escaping.cc.
|
||||
ABSL_CONST_INIT const char kBase64Chars[] =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
ABSL_CONST_INIT const char kWebSafeBase64Chars[] =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
||||
|
||||
|
||||
size_t CalculateBase64EscapedLenInternal(size_t input_len, bool do_padding) {
|
||||
// Base64 encodes three bytes of input at a time. If the input is not
|
||||
// divisible by three, we pad as appropriate.
|
||||
//
|
||||
// Base64 encodes each three bytes of input into four bytes of output.
|
||||
size_t len = (input_len / 3) * 4;
|
||||
|
||||
// Since all base 64 input is an integral number of octets, only the following
|
||||
// cases can arise:
|
||||
if (input_len % 3 == 0) {
|
||||
// (from https://tools.ietf.org/html/rfc3548)
|
||||
// (1) the final quantum of encoding input is an integral multiple of 24
|
||||
// bits; here, the final unit of encoded output will be an integral
|
||||
// multiple of 4 characters with no "=" padding,
|
||||
} else if (input_len % 3 == 1) {
|
||||
// (from https://tools.ietf.org/html/rfc3548)
|
||||
// (2) the final quantum of encoding input is exactly 8 bits; here, the
|
||||
// final unit of encoded output will be two characters followed by two
|
||||
// "=" padding characters, or
|
||||
len += 2;
|
||||
if (do_padding) {
|
||||
len += 2;
|
||||
}
|
||||
} else { // (input_len % 3 == 2)
|
||||
// (from https://tools.ietf.org/html/rfc3548)
|
||||
// (3) the final quantum of encoding input is exactly 16 bits; here, the
|
||||
// final unit of encoded output will be three characters followed by one
|
||||
// "=" padding character.
|
||||
len += 3;
|
||||
if (do_padding) {
|
||||
len += 1;
|
||||
}
|
||||
}
|
||||
|
||||
assert(len >= input_len); // make sure we didn't overflow
|
||||
return len;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Take the input in groups of 4 characters and turn each
|
||||
// character into a code 0 to 63 thus:
|
||||
// A-Z map to 0 to 25
|
||||
// a-z map to 26 to 51
|
||||
// 0-9 map to 52 to 61
|
||||
// +(- for WebSafe) maps to 62
|
||||
// /(_ for WebSafe) maps to 63
|
||||
// There will be four numbers, all less than 64 which can be represented
|
||||
// by a 6 digit binary number (aaaaaa, bbbbbb, cccccc, dddddd respectively).
|
||||
// Arrange the 6 digit binary numbers into three bytes as such:
|
||||
// aaaaaabb bbbbcccc ccdddddd
|
||||
// Equals signs (one or two) are used at the end of the encoded block to
|
||||
// indicate that the text was not an integer multiple of three bytes long.
|
||||
// ----------------------------------------------------------------------
|
||||
size_t Base64EscapeInternal(const unsigned char* src, size_t szsrc, char* dest,
|
||||
size_t szdest, const char* base64,
|
||||
bool do_padding) {
|
||||
static const char kPad64 = '=';
|
||||
|
||||
if (szsrc * 4 > szdest * 3) return 0;
|
||||
|
||||
char* cur_dest = dest;
|
||||
const unsigned char* cur_src = src;
|
||||
|
||||
char* const limit_dest = dest + szdest;
|
||||
const unsigned char* const limit_src = src + szsrc;
|
||||
|
||||
// (from https://tools.ietf.org/html/rfc3548)
|
||||
// Special processing is performed if fewer than 24 bits are available
|
||||
// at the end of the data being encoded. A full encoding quantum is
|
||||
// always completed at the end of a quantity. When fewer than 24 input
|
||||
// bits are available in an input group, zero bits are added (on the
|
||||
// right) to form an integral number of 6-bit groups.
|
||||
//
|
||||
// If do_padding is true, padding at the end of the data is performed. This
|
||||
// output padding uses the '=' character.
|
||||
|
||||
// Three bytes of data encodes to four characters of cyphertext.
|
||||
// So we can pump through three-byte chunks atomically.
|
||||
if (szsrc >= 3) { // "limit_src - 3" is UB if szsrc < 3.
|
||||
while (cur_src < limit_src - 3) { // While we have >= 32 bits.
|
||||
uint32_t in = absl::big_endian::Load32(cur_src) >> 8;
|
||||
|
||||
cur_dest[0] = base64[in >> 18];
|
||||
in &= 0x3FFFF;
|
||||
cur_dest[1] = base64[in >> 12];
|
||||
in &= 0xFFF;
|
||||
cur_dest[2] = base64[in >> 6];
|
||||
in &= 0x3F;
|
||||
cur_dest[3] = base64[in];
|
||||
|
||||
cur_dest += 4;
|
||||
cur_src += 3;
|
||||
}
|
||||
}
|
||||
// To save time, we didn't update szdest or szsrc in the loop. So do it now.
|
||||
szdest = static_cast<size_t>(limit_dest - cur_dest);
|
||||
szsrc = static_cast<size_t>(limit_src - cur_src);
|
||||
|
||||
/* now deal with the tail (<=3 bytes) */
|
||||
switch (szsrc) {
|
||||
case 0:
|
||||
// Nothing left; nothing more to do.
|
||||
break;
|
||||
case 1: {
|
||||
// One byte left: this encodes to two characters, and (optionally)
|
||||
// two pad characters to round out the four-character cypherblock.
|
||||
if (szdest < 2) return 0;
|
||||
uint32_t in = cur_src[0];
|
||||
cur_dest[0] = base64[in >> 2];
|
||||
in &= 0x3;
|
||||
cur_dest[1] = base64[in << 4];
|
||||
cur_dest += 2;
|
||||
szdest -= 2;
|
||||
if (do_padding) {
|
||||
if (szdest < 2) return 0;
|
||||
cur_dest[0] = kPad64;
|
||||
cur_dest[1] = kPad64;
|
||||
cur_dest += 2;
|
||||
szdest -= 2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
// Two bytes left: this encodes to three characters, and (optionally)
|
||||
// one pad character to round out the four-character cypherblock.
|
||||
if (szdest < 3) return 0;
|
||||
uint32_t in = absl::big_endian::Load16(cur_src);
|
||||
cur_dest[0] = base64[in >> 10];
|
||||
in &= 0x3FF;
|
||||
cur_dest[1] = base64[in >> 4];
|
||||
in &= 0x00F;
|
||||
cur_dest[2] = base64[in << 2];
|
||||
cur_dest += 3;
|
||||
szdest -= 3;
|
||||
if (do_padding) {
|
||||
if (szdest < 1) return 0;
|
||||
cur_dest[0] = kPad64;
|
||||
cur_dest += 1;
|
||||
szdest -= 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
// Three bytes left: same as in the big loop above. We can't do this in
|
||||
// the loop because the loop above always reads 4 bytes, and the fourth
|
||||
// byte is past the end of the input.
|
||||
if (szdest < 4) return 0;
|
||||
uint32_t in =
|
||||
(uint32_t{cur_src[0]} << 16) + absl::big_endian::Load16(cur_src + 1);
|
||||
cur_dest[0] = base64[in >> 18];
|
||||
in &= 0x3FFFF;
|
||||
cur_dest[1] = base64[in >> 12];
|
||||
in &= 0xFFF;
|
||||
cur_dest[2] = base64[in >> 6];
|
||||
in &= 0x3F;
|
||||
cur_dest[3] = base64[in];
|
||||
cur_dest += 4;
|
||||
szdest -= 4;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// Should not be reached: blocks of 4 bytes are handled
|
||||
// in the while loop before this switch statement.
|
||||
ABSL_RAW_LOG(FATAL, "Logic problem? szsrc = %zu", szsrc);
|
||||
break;
|
||||
}
|
||||
return static_cast<size_t>(cur_dest - dest);
|
||||
}
|
||||
|
||||
} // namespace strings_internal
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
57
Pods/abseil/absl/strings/internal/escaping.h
generated
Normal file
57
Pods/abseil/absl/strings/internal/escaping.h
generated
Normal file
@@ -0,0 +1,57 @@
|
||||
// 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_ESCAPING_H_
|
||||
#define ABSL_STRINGS_INTERNAL_ESCAPING_H_
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include "absl/strings/internal/resize_uninitialized.h"
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace strings_internal {
|
||||
|
||||
ABSL_CONST_INIT extern const char kBase64Chars[];
|
||||
ABSL_CONST_INIT extern const char kWebSafeBase64Chars[];
|
||||
|
||||
// Calculates the length of a Base64 encoding (RFC 4648) of a string of length
|
||||
// `input_len`, with or without padding per `do_padding`. Note that 'web-safe'
|
||||
// encoding (section 5 of the RFC) does not change this length.
|
||||
size_t CalculateBase64EscapedLenInternal(size_t input_len, bool do_padding);
|
||||
|
||||
// Base64-encodes `src` using the alphabet provided in `base64` (which
|
||||
// determines whether to do web-safe encoding or not) and writes the result to
|
||||
// `dest`. If `do_padding` is true, `dest` is padded with '=' chars until its
|
||||
// length is a multiple of 3. Returns the length of `dest`.
|
||||
size_t Base64EscapeInternal(const unsigned char* src, size_t szsrc, char* dest,
|
||||
size_t szdest, const char* base64, bool do_padding);
|
||||
template <typename String>
|
||||
void Base64EscapeInternal(const unsigned char* src, size_t szsrc, String* dest,
|
||||
bool do_padding, const char* base64_chars) {
|
||||
const size_t calc_escaped_size =
|
||||
CalculateBase64EscapedLenInternal(szsrc, do_padding);
|
||||
STLStringResizeUninitialized(dest, calc_escaped_size);
|
||||
|
||||
const size_t escaped_len = Base64EscapeInternal(
|
||||
src, szsrc, &(*dest)[0], dest->size(), base64_chars, do_padding);
|
||||
assert(calc_escaped_size == escaped_len);
|
||||
dest->erase(escaped_len);
|
||||
}
|
||||
|
||||
} // namespace strings_internal
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
|
||||
#endif // ABSL_STRINGS_INTERNAL_ESCAPING_H_
|
||||
44
Pods/abseil/absl/strings/internal/has_absl_stringify.h
generated
Normal file
44
Pods/abseil/absl/strings/internal/has_absl_stringify.h
generated
Normal file
@@ -0,0 +1,44 @@
|
||||
// Copyright 2024 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_HAS_ABSL_STRINGIFY_H_
|
||||
#define ABSL_STRINGS_INTERNAL_HAS_ABSL_STRINGIFY_H_
|
||||
|
||||
#include "absl/strings/has_absl_stringify.h"
|
||||
|
||||
#include "absl/base/config.h"
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
|
||||
namespace strings_internal {
|
||||
|
||||
// This exists to fix a circular dependency problem with the GoogleTest release.
|
||||
// GoogleTest referenced this internal file and this internal trait. Since
|
||||
// simultaneous releases are not possible since once release must reference
|
||||
// another, we will temporarily add this back.
|
||||
// https://github.com/google/googletest/blob/v1.14.x/googletest/include/gtest/gtest-printers.h#L119
|
||||
//
|
||||
// This file can be deleted after the next Abseil and GoogleTest release.
|
||||
//
|
||||
// https://github.com/google/googletest/pull/4368#issuecomment-1717699895
|
||||
// https://github.com/google/googletest/pull/4368#issuecomment-1717699895
|
||||
using ::absl::HasAbslStringify;
|
||||
|
||||
} // namespace strings_internal
|
||||
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
|
||||
#endif // ABSL_STRINGS_INTERNAL_HAS_ABSL_STRINGIFY_H_
|
||||
48
Pods/abseil/absl/strings/internal/memutil.cc
generated
Normal file
48
Pods/abseil/absl/strings/internal/memutil.cc
generated
Normal file
@@ -0,0 +1,48 @@
|
||||
// Copyright 2017 The Abseil Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// 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/memutil.h"
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#include "absl/strings/ascii.h"
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace strings_internal {
|
||||
|
||||
int memcasecmp(const char* s1, const char* s2, size_t len) {
|
||||
const unsigned char* us1 = reinterpret_cast<const unsigned char*>(s1);
|
||||
const unsigned char* us2 = reinterpret_cast<const unsigned char*>(s2);
|
||||
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
unsigned char c1 = us1[i];
|
||||
unsigned char c2 = us2[i];
|
||||
// If bytes are the same, they will be the same when converted to lower.
|
||||
// So we only need to convert if bytes are not equal.
|
||||
// NOTE(b/308193381): We do not use `absl::ascii_tolower` here in order
|
||||
// to avoid its lookup table and improve performance.
|
||||
if (c1 != c2) {
|
||||
c1 = c1 >= 'A' && c1 <= 'Z' ? c1 - 'A' + 'a' : c1;
|
||||
c2 = c2 >= 'A' && c2 <= 'Z' ? c2 - 'A' + 'a' : c2;
|
||||
const int diff = int{c1} - int{c2};
|
||||
if (diff != 0) return diff;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace strings_internal
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
40
Pods/abseil/absl/strings/internal/memutil.h
generated
Normal file
40
Pods/abseil/absl/strings/internal/memutil.h
generated
Normal file
@@ -0,0 +1,40 @@
|
||||
//
|
||||
// Copyright 2017 The Abseil Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// 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_MEMUTIL_H_
|
||||
#define ABSL_STRINGS_INTERNAL_MEMUTIL_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
|
||||
#include "absl/base/port.h" // disable some warnings on Windows
|
||||
#include "absl/strings/ascii.h" // for absl::ascii_tolower
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace strings_internal {
|
||||
|
||||
// Performs a byte-by-byte comparison of `len` bytes of the strings `s1` and
|
||||
// `s2`, ignoring the case of the characters. It returns an integer less than,
|
||||
// equal to, or greater than zero if `s1` is found, respectively, to be less
|
||||
// than, to match, or be greater than `s2`.
|
||||
int memcasecmp(const char* s1, const char* s2, size_t len);
|
||||
|
||||
} // namespace strings_internal
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
|
||||
#endif // ABSL_STRINGS_INTERNAL_MEMUTIL_H_
|
||||
43
Pods/abseil/absl/strings/internal/ostringstream.cc
generated
Normal file
43
Pods/abseil/absl/strings/internal/ostringstream.cc
generated
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright 2017 The Abseil Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// 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/ostringstream.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <ios>
|
||||
#include <streambuf>
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace strings_internal {
|
||||
|
||||
OStringStream::Streambuf::int_type OStringStream::Streambuf::overflow(int c) {
|
||||
assert(str_);
|
||||
if (!std::streambuf::traits_type::eq_int_type(
|
||||
c, std::streambuf::traits_type::eof()))
|
||||
str_->push_back(static_cast<char>(c));
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::streamsize OStringStream::Streambuf::xsputn(const char* s,
|
||||
std::streamsize n) {
|
||||
assert(str_);
|
||||
str_->append(s, static_cast<size_t>(n));
|
||||
return n;
|
||||
}
|
||||
|
||||
} // namespace strings_internal
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
114
Pods/abseil/absl/strings/internal/ostringstream.h
generated
Normal file
114
Pods/abseil/absl/strings/internal/ostringstream.h
generated
Normal file
@@ -0,0 +1,114 @@
|
||||
// Copyright 2017 The Abseil Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// 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_OSTRINGSTREAM_H_
|
||||
#define ABSL_STRINGS_INTERNAL_OSTRINGSTREAM_H_
|
||||
|
||||
#include <cassert>
|
||||
#include <ios>
|
||||
#include <ostream>
|
||||
#include <streambuf>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "absl/base/config.h"
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace strings_internal {
|
||||
|
||||
// The same as std::ostringstream but appends to a user-specified std::string,
|
||||
// and is faster. It is ~70% faster to create, ~50% faster to write to, and
|
||||
// completely free to extract the result std::string.
|
||||
//
|
||||
// std::string s;
|
||||
// OStringStream strm(&s);
|
||||
// strm << 42 << ' ' << 3.14; // appends to `s`
|
||||
//
|
||||
// The stream object doesn't have to be named. Starting from C++11 operator<<
|
||||
// works with rvalues of std::ostream.
|
||||
//
|
||||
// std::string s;
|
||||
// OStringStream(&s) << 42 << ' ' << 3.14; // appends to `s`
|
||||
//
|
||||
// OStringStream is faster to create than std::ostringstream but it's still
|
||||
// relatively slow. Avoid creating multiple streams where a single stream will
|
||||
// do.
|
||||
//
|
||||
// Creates unnecessary instances of OStringStream: slow.
|
||||
//
|
||||
// std::string s;
|
||||
// OStringStream(&s) << 42;
|
||||
// OStringStream(&s) << ' ';
|
||||
// OStringStream(&s) << 3.14;
|
||||
//
|
||||
// Creates a single instance of OStringStream and reuses it: fast.
|
||||
//
|
||||
// std::string s;
|
||||
// OStringStream strm(&s);
|
||||
// strm << 42;
|
||||
// strm << ' ';
|
||||
// strm << 3.14;
|
||||
//
|
||||
// Note: flush() has no effect. No reason to call it.
|
||||
class OStringStream final : public std::ostream {
|
||||
public:
|
||||
// The argument can be null, in which case you'll need to call str(p) with a
|
||||
// non-null argument before you can write to the stream.
|
||||
//
|
||||
// The destructor of OStringStream doesn't use the std::string. It's OK to
|
||||
// destroy the std::string before the stream.
|
||||
explicit OStringStream(std::string* str)
|
||||
: std::ostream(&buf_), buf_(str) {}
|
||||
OStringStream(OStringStream&& that)
|
||||
: std::ostream(std::move(static_cast<std::ostream&>(that))),
|
||||
buf_(that.buf_) {
|
||||
rdbuf(&buf_);
|
||||
}
|
||||
OStringStream& operator=(OStringStream&& that) {
|
||||
std::ostream::operator=(std::move(static_cast<std::ostream&>(that)));
|
||||
buf_ = that.buf_;
|
||||
rdbuf(&buf_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::string* str() { return buf_.str(); }
|
||||
const std::string* str() const { return buf_.str(); }
|
||||
void str(std::string* str) { buf_.str(str); }
|
||||
|
||||
private:
|
||||
class Streambuf final : public std::streambuf {
|
||||
public:
|
||||
explicit Streambuf(std::string* str) : str_(str) {}
|
||||
Streambuf(const Streambuf&) = default;
|
||||
Streambuf& operator=(const Streambuf&) = default;
|
||||
|
||||
std::string* str() { return str_; }
|
||||
const std::string* str() const { return str_; }
|
||||
void str(std::string* str) { str_ = str; }
|
||||
|
||||
protected:
|
||||
int_type overflow(int c) override;
|
||||
std::streamsize xsputn(const char* s, std::streamsize n) override;
|
||||
|
||||
private:
|
||||
std::string* str_;
|
||||
} buf_;
|
||||
};
|
||||
|
||||
} // namespace strings_internal
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
|
||||
#endif // ABSL_STRINGS_INTERNAL_OSTRINGSTREAM_H_
|
||||
119
Pods/abseil/absl/strings/internal/resize_uninitialized.h
generated
Normal file
119
Pods/abseil/absl/strings/internal/resize_uninitialized.h
generated
Normal file
@@ -0,0 +1,119 @@
|
||||
//
|
||||
// Copyright 2017 The Abseil Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// 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_RESIZE_UNINITIALIZED_H_
|
||||
#define ABSL_STRINGS_INTERNAL_RESIZE_UNINITIALIZED_H_
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "absl/base/port.h"
|
||||
#include "absl/meta/type_traits.h" // for void_t
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace strings_internal {
|
||||
|
||||
// In this type trait, we look for a __resize_default_init member function, and
|
||||
// we use it if available, otherwise, we use resize. We provide HasMember to
|
||||
// indicate whether __resize_default_init is present.
|
||||
template <typename string_type, typename = void>
|
||||
struct ResizeUninitializedTraits {
|
||||
using HasMember = std::false_type;
|
||||
static void Resize(string_type* s, size_t new_size) { s->resize(new_size); }
|
||||
};
|
||||
|
||||
// __resize_default_init is provided by libc++ >= 8.0
|
||||
template <typename string_type>
|
||||
struct ResizeUninitializedTraits<
|
||||
string_type, absl::void_t<decltype(std::declval<string_type&>()
|
||||
.__resize_default_init(237))> > {
|
||||
using HasMember = std::true_type;
|
||||
static void Resize(string_type* s, size_t new_size) {
|
||||
s->__resize_default_init(new_size);
|
||||
}
|
||||
};
|
||||
|
||||
// Returns true if the std::string implementation supports a resize where
|
||||
// the new characters added to the std::string are left untouched.
|
||||
//
|
||||
// (A better name might be "STLStringSupportsUninitializedResize", alluding to
|
||||
// the previous function.)
|
||||
template <typename string_type>
|
||||
inline constexpr bool STLStringSupportsNontrashingResize(string_type*) {
|
||||
return ResizeUninitializedTraits<string_type>::HasMember::value;
|
||||
}
|
||||
|
||||
// Like str->resize(new_size), except any new characters added to "*str" as a
|
||||
// result of resizing may be left uninitialized, rather than being filled with
|
||||
// '0' bytes. Typically used when code is then going to overwrite the backing
|
||||
// store of the std::string with known data.
|
||||
template <typename string_type, typename = void>
|
||||
inline void STLStringResizeUninitialized(string_type* s, size_t new_size) {
|
||||
ResizeUninitializedTraits<string_type>::Resize(s, new_size);
|
||||
}
|
||||
|
||||
// Used to ensure exponential growth so that the amortized complexity of
|
||||
// increasing the string size by a small amount is O(1), in contrast to
|
||||
// O(str->size()) in the case of precise growth.
|
||||
template <typename string_type>
|
||||
void STLStringReserveAmortized(string_type* s, size_t new_size) {
|
||||
const size_t cap = s->capacity();
|
||||
if (new_size > cap) {
|
||||
// Make sure to always grow by at least a factor of 2x.
|
||||
s->reserve((std::max)(new_size, 2 * cap));
|
||||
}
|
||||
}
|
||||
|
||||
// In this type trait, we look for an __append_default_init member function, and
|
||||
// we use it if available, otherwise, we use append.
|
||||
template <typename string_type, typename = void>
|
||||
struct AppendUninitializedTraits {
|
||||
static void Append(string_type* s, size_t n) {
|
||||
s->append(n, typename string_type::value_type());
|
||||
}
|
||||
};
|
||||
|
||||
template <typename string_type>
|
||||
struct AppendUninitializedTraits<
|
||||
string_type, absl::void_t<decltype(std::declval<string_type&>()
|
||||
.__append_default_init(237))> > {
|
||||
static void Append(string_type* s, size_t n) {
|
||||
s->__append_default_init(n);
|
||||
}
|
||||
};
|
||||
|
||||
// Like STLStringResizeUninitialized(str, new_size), except guaranteed to use
|
||||
// exponential growth so that the amortized complexity of increasing the string
|
||||
// size by a small amount is O(1), in contrast to O(str->size()) in the case of
|
||||
// precise growth.
|
||||
template <typename string_type>
|
||||
void STLStringResizeUninitializedAmortized(string_type* s, size_t new_size) {
|
||||
const size_t size = s->size();
|
||||
if (new_size > size) {
|
||||
AppendUninitializedTraits<string_type>::Append(s, new_size - size);
|
||||
} else {
|
||||
s->erase(new_size);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace strings_internal
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
|
||||
#endif // ABSL_STRINGS_INTERNAL_RESIZE_UNINITIALIZED_H_
|
||||
248
Pods/abseil/absl/strings/internal/stl_type_traits.h
generated
Normal file
248
Pods/abseil/absl/strings/internal/stl_type_traits.h
generated
Normal file
@@ -0,0 +1,248 @@
|
||||
// Copyright 2017 The Abseil Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
// The file provides the IsStrictlyBaseOfAndConvertibleToSTLContainer type
|
||||
// trait metafunction to assist in working with the _GLIBCXX_DEBUG debug
|
||||
// wrappers of STL containers.
|
||||
//
|
||||
// DO NOT INCLUDE THIS FILE DIRECTLY. Use this file by including
|
||||
// absl/strings/str_split.h.
|
||||
//
|
||||
// IWYU pragma: private, include "absl/strings/str_split.h"
|
||||
|
||||
#ifndef ABSL_STRINGS_INTERNAL_STL_TYPE_TRAITS_H_
|
||||
#define ABSL_STRINGS_INTERNAL_STL_TYPE_TRAITS_H_
|
||||
|
||||
#include <array>
|
||||
#include <bitset>
|
||||
#include <deque>
|
||||
#include <forward_list>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/meta/type_traits.h"
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace strings_internal {
|
||||
|
||||
template <typename C, template <typename...> class T>
|
||||
struct IsSpecializationImpl : std::false_type {};
|
||||
template <template <typename...> class T, typename... Args>
|
||||
struct IsSpecializationImpl<T<Args...>, T> : std::true_type {};
|
||||
template <typename C, template <typename...> class T>
|
||||
using IsSpecialization = IsSpecializationImpl<absl::decay_t<C>, T>;
|
||||
|
||||
template <typename C>
|
||||
struct IsArrayImpl : std::false_type {};
|
||||
template <template <typename, size_t> class A, typename T, size_t N>
|
||||
struct IsArrayImpl<A<T, N>> : std::is_same<A<T, N>, std::array<T, N>> {};
|
||||
template <typename C>
|
||||
using IsArray = IsArrayImpl<absl::decay_t<C>>;
|
||||
|
||||
template <typename C>
|
||||
struct IsBitsetImpl : std::false_type {};
|
||||
template <template <size_t> class B, size_t N>
|
||||
struct IsBitsetImpl<B<N>> : std::is_same<B<N>, std::bitset<N>> {};
|
||||
template <typename C>
|
||||
using IsBitset = IsBitsetImpl<absl::decay_t<C>>;
|
||||
|
||||
template <typename C>
|
||||
struct IsSTLContainer
|
||||
: absl::disjunction<
|
||||
IsArray<C>, IsBitset<C>, IsSpecialization<C, std::deque>,
|
||||
IsSpecialization<C, std::forward_list>,
|
||||
IsSpecialization<C, std::list>, IsSpecialization<C, std::map>,
|
||||
IsSpecialization<C, std::multimap>, IsSpecialization<C, std::set>,
|
||||
IsSpecialization<C, std::multiset>,
|
||||
IsSpecialization<C, std::unordered_map>,
|
||||
IsSpecialization<C, std::unordered_multimap>,
|
||||
IsSpecialization<C, std::unordered_set>,
|
||||
IsSpecialization<C, std::unordered_multiset>,
|
||||
IsSpecialization<C, std::vector>> {};
|
||||
|
||||
template <typename C, template <typename...> class T, typename = void>
|
||||
struct IsBaseOfSpecializationImpl : std::false_type {};
|
||||
// IsBaseOfSpecializationImpl needs multiple partial specializations to SFINAE
|
||||
// on the existence of container dependent types and plug them into the STL
|
||||
// template.
|
||||
template <typename C, template <typename, typename> class T>
|
||||
struct IsBaseOfSpecializationImpl<
|
||||
C, T, absl::void_t<typename C::value_type, typename C::allocator_type>>
|
||||
: std::is_base_of<C,
|
||||
T<typename C::value_type, typename C::allocator_type>> {};
|
||||
template <typename C, template <typename, typename, typename> class T>
|
||||
struct IsBaseOfSpecializationImpl<
|
||||
C, T,
|
||||
absl::void_t<typename C::key_type, typename C::key_compare,
|
||||
typename C::allocator_type>>
|
||||
: std::is_base_of<C, T<typename C::key_type, typename C::key_compare,
|
||||
typename C::allocator_type>> {};
|
||||
template <typename C, template <typename, typename, typename, typename> class T>
|
||||
struct IsBaseOfSpecializationImpl<
|
||||
C, T,
|
||||
absl::void_t<typename C::key_type, typename C::mapped_type,
|
||||
typename C::key_compare, typename C::allocator_type>>
|
||||
: std::is_base_of<C,
|
||||
T<typename C::key_type, typename C::mapped_type,
|
||||
typename C::key_compare, typename C::allocator_type>> {
|
||||
};
|
||||
template <typename C, template <typename, typename, typename, typename> class T>
|
||||
struct IsBaseOfSpecializationImpl<
|
||||
C, T,
|
||||
absl::void_t<typename C::key_type, typename C::hasher,
|
||||
typename C::key_equal, typename C::allocator_type>>
|
||||
: std::is_base_of<C, T<typename C::key_type, typename C::hasher,
|
||||
typename C::key_equal, typename C::allocator_type>> {
|
||||
};
|
||||
template <typename C,
|
||||
template <typename, typename, typename, typename, typename> class T>
|
||||
struct IsBaseOfSpecializationImpl<
|
||||
C, T,
|
||||
absl::void_t<typename C::key_type, typename C::mapped_type,
|
||||
typename C::hasher, typename C::key_equal,
|
||||
typename C::allocator_type>>
|
||||
: std::is_base_of<C, T<typename C::key_type, typename C::mapped_type,
|
||||
typename C::hasher, typename C::key_equal,
|
||||
typename C::allocator_type>> {};
|
||||
template <typename C, template <typename...> class T>
|
||||
using IsBaseOfSpecialization = IsBaseOfSpecializationImpl<absl::decay_t<C>, T>;
|
||||
|
||||
template <typename C>
|
||||
struct IsBaseOfArrayImpl : std::false_type {};
|
||||
template <template <typename, size_t> class A, typename T, size_t N>
|
||||
struct IsBaseOfArrayImpl<A<T, N>> : std::is_base_of<A<T, N>, std::array<T, N>> {
|
||||
};
|
||||
template <typename C>
|
||||
using IsBaseOfArray = IsBaseOfArrayImpl<absl::decay_t<C>>;
|
||||
|
||||
template <typename C>
|
||||
struct IsBaseOfBitsetImpl : std::false_type {};
|
||||
template <template <size_t> class B, size_t N>
|
||||
struct IsBaseOfBitsetImpl<B<N>> : std::is_base_of<B<N>, std::bitset<N>> {};
|
||||
template <typename C>
|
||||
using IsBaseOfBitset = IsBaseOfBitsetImpl<absl::decay_t<C>>;
|
||||
|
||||
template <typename C>
|
||||
struct IsBaseOfSTLContainer
|
||||
: absl::disjunction<IsBaseOfArray<C>, IsBaseOfBitset<C>,
|
||||
IsBaseOfSpecialization<C, std::deque>,
|
||||
IsBaseOfSpecialization<C, std::forward_list>,
|
||||
IsBaseOfSpecialization<C, std::list>,
|
||||
IsBaseOfSpecialization<C, std::map>,
|
||||
IsBaseOfSpecialization<C, std::multimap>,
|
||||
IsBaseOfSpecialization<C, std::set>,
|
||||
IsBaseOfSpecialization<C, std::multiset>,
|
||||
IsBaseOfSpecialization<C, std::unordered_map>,
|
||||
IsBaseOfSpecialization<C, std::unordered_multimap>,
|
||||
IsBaseOfSpecialization<C, std::unordered_set>,
|
||||
IsBaseOfSpecialization<C, std::unordered_multiset>,
|
||||
IsBaseOfSpecialization<C, std::vector>> {};
|
||||
|
||||
template <typename C, template <typename...> class T, typename = void>
|
||||
struct IsConvertibleToSpecializationImpl : std::false_type {};
|
||||
// IsConvertibleToSpecializationImpl needs multiple partial specializations to
|
||||
// SFINAE on the existence of container dependent types and plug them into the
|
||||
// STL template.
|
||||
template <typename C, template <typename, typename> class T>
|
||||
struct IsConvertibleToSpecializationImpl<
|
||||
C, T, absl::void_t<typename C::value_type, typename C::allocator_type>>
|
||||
: std::is_convertible<
|
||||
C, T<typename C::value_type, typename C::allocator_type>> {};
|
||||
template <typename C, template <typename, typename, typename> class T>
|
||||
struct IsConvertibleToSpecializationImpl<
|
||||
C, T,
|
||||
absl::void_t<typename C::key_type, typename C::key_compare,
|
||||
typename C::allocator_type>>
|
||||
: std::is_convertible<C, T<typename C::key_type, typename C::key_compare,
|
||||
typename C::allocator_type>> {};
|
||||
template <typename C, template <typename, typename, typename, typename> class T>
|
||||
struct IsConvertibleToSpecializationImpl<
|
||||
C, T,
|
||||
absl::void_t<typename C::key_type, typename C::mapped_type,
|
||||
typename C::key_compare, typename C::allocator_type>>
|
||||
: std::is_convertible<
|
||||
C, T<typename C::key_type, typename C::mapped_type,
|
||||
typename C::key_compare, typename C::allocator_type>> {};
|
||||
template <typename C, template <typename, typename, typename, typename> class T>
|
||||
struct IsConvertibleToSpecializationImpl<
|
||||
C, T,
|
||||
absl::void_t<typename C::key_type, typename C::hasher,
|
||||
typename C::key_equal, typename C::allocator_type>>
|
||||
: std::is_convertible<
|
||||
C, T<typename C::key_type, typename C::hasher, typename C::key_equal,
|
||||
typename C::allocator_type>> {};
|
||||
template <typename C,
|
||||
template <typename, typename, typename, typename, typename> class T>
|
||||
struct IsConvertibleToSpecializationImpl<
|
||||
C, T,
|
||||
absl::void_t<typename C::key_type, typename C::mapped_type,
|
||||
typename C::hasher, typename C::key_equal,
|
||||
typename C::allocator_type>>
|
||||
: std::is_convertible<C, T<typename C::key_type, typename C::mapped_type,
|
||||
typename C::hasher, typename C::key_equal,
|
||||
typename C::allocator_type>> {};
|
||||
template <typename C, template <typename...> class T>
|
||||
using IsConvertibleToSpecialization =
|
||||
IsConvertibleToSpecializationImpl<absl::decay_t<C>, T>;
|
||||
|
||||
template <typename C>
|
||||
struct IsConvertibleToArrayImpl : std::false_type {};
|
||||
template <template <typename, size_t> class A, typename T, size_t N>
|
||||
struct IsConvertibleToArrayImpl<A<T, N>>
|
||||
: std::is_convertible<A<T, N>, std::array<T, N>> {};
|
||||
template <typename C>
|
||||
using IsConvertibleToArray = IsConvertibleToArrayImpl<absl::decay_t<C>>;
|
||||
|
||||
template <typename C>
|
||||
struct IsConvertibleToBitsetImpl : std::false_type {};
|
||||
template <template <size_t> class B, size_t N>
|
||||
struct IsConvertibleToBitsetImpl<B<N>>
|
||||
: std::is_convertible<B<N>, std::bitset<N>> {};
|
||||
template <typename C>
|
||||
using IsConvertibleToBitset = IsConvertibleToBitsetImpl<absl::decay_t<C>>;
|
||||
|
||||
template <typename C>
|
||||
struct IsConvertibleToSTLContainer
|
||||
: absl::disjunction<
|
||||
IsConvertibleToArray<C>, IsConvertibleToBitset<C>,
|
||||
IsConvertibleToSpecialization<C, std::deque>,
|
||||
IsConvertibleToSpecialization<C, std::forward_list>,
|
||||
IsConvertibleToSpecialization<C, std::list>,
|
||||
IsConvertibleToSpecialization<C, std::map>,
|
||||
IsConvertibleToSpecialization<C, std::multimap>,
|
||||
IsConvertibleToSpecialization<C, std::set>,
|
||||
IsConvertibleToSpecialization<C, std::multiset>,
|
||||
IsConvertibleToSpecialization<C, std::unordered_map>,
|
||||
IsConvertibleToSpecialization<C, std::unordered_multimap>,
|
||||
IsConvertibleToSpecialization<C, std::unordered_set>,
|
||||
IsConvertibleToSpecialization<C, std::unordered_multiset>,
|
||||
IsConvertibleToSpecialization<C, std::vector>> {};
|
||||
|
||||
template <typename C>
|
||||
struct IsStrictlyBaseOfAndConvertibleToSTLContainer
|
||||
: absl::conjunction<absl::negation<IsSTLContainer<C>>,
|
||||
IsBaseOfSTLContainer<C>,
|
||||
IsConvertibleToSTLContainer<C>> {};
|
||||
|
||||
} // namespace strings_internal
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
#endif // ABSL_STRINGS_INTERNAL_STL_TYPE_TRAITS_H_
|
||||
671
Pods/abseil/absl/strings/internal/str_format/arg.cc
generated
Normal file
671
Pods/abseil/absl/strings/internal/str_format/arg.cc
generated
Normal file
@@ -0,0 +1,671 @@
|
||||
// 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
|
||||
//
|
||||
#include "absl/strings/internal/str_format/arg.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <cwchar>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
#include "absl/base/config.h"
|
||||
#include "absl/base/optimization.h"
|
||||
#include "absl/container/fixed_array.h"
|
||||
#include "absl/numeric/int128.h"
|
||||
#include "absl/strings/internal/str_format/extension.h"
|
||||
#include "absl/strings/internal/str_format/float_conversion.h"
|
||||
#include "absl/strings/numbers.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
|
||||
#if defined(ABSL_HAVE_STD_STRING_VIEW)
|
||||
#include <string_view>
|
||||
#endif
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace str_format_internal {
|
||||
namespace {
|
||||
|
||||
// Reduce *capacity by s.size(), clipped to a 0 minimum.
|
||||
void ReducePadding(string_view s, size_t *capacity) {
|
||||
*capacity = Excess(s.size(), *capacity);
|
||||
}
|
||||
|
||||
// Reduce *capacity by n, clipped to a 0 minimum.
|
||||
void ReducePadding(size_t n, size_t *capacity) {
|
||||
*capacity = Excess(n, *capacity);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
struct MakeUnsigned : std::make_unsigned<T> {};
|
||||
template <>
|
||||
struct MakeUnsigned<absl::int128> {
|
||||
using type = absl::uint128;
|
||||
};
|
||||
template <>
|
||||
struct MakeUnsigned<absl::uint128> {
|
||||
using type = absl::uint128;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct IsSigned : std::is_signed<T> {};
|
||||
template <>
|
||||
struct IsSigned<absl::int128> : std::true_type {};
|
||||
template <>
|
||||
struct IsSigned<absl::uint128> : std::false_type {};
|
||||
|
||||
// Integral digit printer.
|
||||
// Call one of the PrintAs* routines after construction once.
|
||||
// Use with_neg_and_zero/without_neg_or_zero/is_negative to access the results.
|
||||
class IntDigits {
|
||||
public:
|
||||
// Print the unsigned integer as octal.
|
||||
// Supports unsigned integral types and uint128.
|
||||
template <typename T>
|
||||
void PrintAsOct(T v) {
|
||||
static_assert(!IsSigned<T>::value, "");
|
||||
char *p = storage_ + sizeof(storage_);
|
||||
do {
|
||||
*--p = static_cast<char>('0' + (static_cast<size_t>(v) & 7));
|
||||
v >>= 3;
|
||||
} while (v);
|
||||
start_ = p;
|
||||
size_ = static_cast<size_t>(storage_ + sizeof(storage_) - p);
|
||||
}
|
||||
|
||||
// Print the signed or unsigned integer as decimal.
|
||||
// Supports all integral types.
|
||||
template <typename T>
|
||||
void PrintAsDec(T v) {
|
||||
static_assert(std::is_integral<T>::value, "");
|
||||
start_ = storage_;
|
||||
size_ = static_cast<size_t>(numbers_internal::FastIntToBuffer(v, storage_) -
|
||||
storage_);
|
||||
}
|
||||
|
||||
void PrintAsDec(int128 v) {
|
||||
auto u = static_cast<uint128>(v);
|
||||
bool add_neg = false;
|
||||
if (v < 0) {
|
||||
add_neg = true;
|
||||
u = uint128{} - u;
|
||||
}
|
||||
PrintAsDec(u, add_neg);
|
||||
}
|
||||
|
||||
void PrintAsDec(uint128 v, bool add_neg = false) {
|
||||
// This function can be sped up if needed. We can call FastIntToBuffer
|
||||
// twice, or fix FastIntToBuffer to support uint128.
|
||||
char *p = storage_ + sizeof(storage_);
|
||||
do {
|
||||
p -= 2;
|
||||
numbers_internal::PutTwoDigits(static_cast<uint32_t>(v % 100), p);
|
||||
v /= 100;
|
||||
} while (v);
|
||||
if (p[0] == '0') {
|
||||
// We printed one too many hexits.
|
||||
++p;
|
||||
}
|
||||
if (add_neg) {
|
||||
*--p = '-';
|
||||
}
|
||||
size_ = static_cast<size_t>(storage_ + sizeof(storage_) - p);
|
||||
start_ = p;
|
||||
}
|
||||
|
||||
// Print the unsigned integer as hex using lowercase.
|
||||
// Supports unsigned integral types and uint128.
|
||||
template <typename T>
|
||||
void PrintAsHexLower(T v) {
|
||||
static_assert(!IsSigned<T>::value, "");
|
||||
char *p = storage_ + sizeof(storage_);
|
||||
|
||||
do {
|
||||
p -= 2;
|
||||
constexpr const char* table = numbers_internal::kHexTable;
|
||||
std::memcpy(p, table + 2 * (static_cast<size_t>(v) & 0xFF), 2);
|
||||
if (sizeof(T) == 1) break;
|
||||
v >>= 8;
|
||||
} while (v);
|
||||
if (p[0] == '0') {
|
||||
// We printed one too many digits.
|
||||
++p;
|
||||
}
|
||||
start_ = p;
|
||||
size_ = static_cast<size_t>(storage_ + sizeof(storage_) - p);
|
||||
}
|
||||
|
||||
// Print the unsigned integer as hex using uppercase.
|
||||
// Supports unsigned integral types and uint128.
|
||||
template <typename T>
|
||||
void PrintAsHexUpper(T v) {
|
||||
static_assert(!IsSigned<T>::value, "");
|
||||
char *p = storage_ + sizeof(storage_);
|
||||
|
||||
// kHexTable is only lowercase, so do it manually for uppercase.
|
||||
do {
|
||||
*--p = "0123456789ABCDEF"[static_cast<size_t>(v) & 15];
|
||||
v >>= 4;
|
||||
} while (v);
|
||||
start_ = p;
|
||||
size_ = static_cast<size_t>(storage_ + sizeof(storage_) - p);
|
||||
}
|
||||
|
||||
// The printed value including the '-' sign if available.
|
||||
// For inputs of value `0`, this will return "0"
|
||||
string_view with_neg_and_zero() const { return {start_, size_}; }
|
||||
|
||||
// The printed value not including the '-' sign.
|
||||
// For inputs of value `0`, this will return "".
|
||||
string_view without_neg_or_zero() const {
|
||||
static_assert('-' < '0', "The check below verifies both.");
|
||||
size_t advance = start_[0] <= '0' ? 1 : 0;
|
||||
return {start_ + advance, size_ - advance};
|
||||
}
|
||||
|
||||
bool is_negative() const { return start_[0] == '-'; }
|
||||
|
||||
private:
|
||||
const char *start_;
|
||||
size_t size_;
|
||||
// Max size: 128 bit value as octal -> 43 digits, plus sign char
|
||||
char storage_[128 / 3 + 1 + 1];
|
||||
};
|
||||
|
||||
// Note: 'o' conversions do not have a base indicator, it's just that
|
||||
// the '#' flag is specified to modify the precision for 'o' conversions.
|
||||
string_view BaseIndicator(const IntDigits &as_digits,
|
||||
const FormatConversionSpecImpl conv) {
|
||||
// always show 0x for %p.
|
||||
bool alt = conv.has_alt_flag() ||
|
||||
conv.conversion_char() == FormatConversionCharInternal::p;
|
||||
bool hex = (conv.conversion_char() == FormatConversionCharInternal::x ||
|
||||
conv.conversion_char() == FormatConversionCharInternal::X ||
|
||||
conv.conversion_char() == FormatConversionCharInternal::p);
|
||||
// From the POSIX description of '#' flag:
|
||||
// "For x or X conversion specifiers, a non-zero result shall have
|
||||
// 0x (or 0X) prefixed to it."
|
||||
if (alt && hex && !as_digits.without_neg_or_zero().empty()) {
|
||||
return conv.conversion_char() == FormatConversionCharInternal::X ? "0X"
|
||||
: "0x";
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
string_view SignColumn(bool neg, const FormatConversionSpecImpl conv) {
|
||||
if (conv.conversion_char() == FormatConversionCharInternal::d ||
|
||||
conv.conversion_char() == FormatConversionCharInternal::i) {
|
||||
if (neg) return "-";
|
||||
if (conv.has_show_pos_flag()) return "+";
|
||||
if (conv.has_sign_col_flag()) return " ";
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool ConvertCharImpl(char v,
|
||||
const FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink) {
|
||||
size_t fill = 0;
|
||||
if (conv.width() >= 0)
|
||||
fill = static_cast<size_t>(conv.width());
|
||||
ReducePadding(1, &fill);
|
||||
if (!conv.has_left_flag()) sink->Append(fill, ' ');
|
||||
sink->Append(1, v);
|
||||
if (conv.has_left_flag()) sink->Append(fill, ' ');
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ConvertIntImplInnerSlow(const IntDigits &as_digits,
|
||||
const FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink) {
|
||||
// Print as a sequence of Substrings:
|
||||
// [left_spaces][sign][base_indicator][zeroes][formatted][right_spaces]
|
||||
size_t fill = 0;
|
||||
if (conv.width() >= 0)
|
||||
fill = static_cast<size_t>(conv.width());
|
||||
|
||||
string_view formatted = as_digits.without_neg_or_zero();
|
||||
ReducePadding(formatted, &fill);
|
||||
|
||||
string_view sign = SignColumn(as_digits.is_negative(), conv);
|
||||
ReducePadding(sign, &fill);
|
||||
|
||||
string_view base_indicator = BaseIndicator(as_digits, conv);
|
||||
ReducePadding(base_indicator, &fill);
|
||||
|
||||
bool precision_specified = conv.precision() >= 0;
|
||||
size_t precision =
|
||||
precision_specified ? static_cast<size_t>(conv.precision()) : size_t{1};
|
||||
|
||||
if (conv.has_alt_flag() &&
|
||||
conv.conversion_char() == FormatConversionCharInternal::o) {
|
||||
// From POSIX description of the '#' (alt) flag:
|
||||
// "For o conversion, it increases the precision (if necessary) to
|
||||
// force the first digit of the result to be zero."
|
||||
if (formatted.empty() || *formatted.begin() != '0') {
|
||||
size_t needed = formatted.size() + 1;
|
||||
precision = std::max(precision, needed);
|
||||
}
|
||||
}
|
||||
|
||||
size_t num_zeroes = Excess(formatted.size(), precision);
|
||||
ReducePadding(num_zeroes, &fill);
|
||||
|
||||
size_t num_left_spaces = !conv.has_left_flag() ? fill : 0;
|
||||
size_t num_right_spaces = conv.has_left_flag() ? fill : 0;
|
||||
|
||||
// From POSIX description of the '0' (zero) flag:
|
||||
// "For d, i, o, u, x, and X conversion specifiers, if a precision
|
||||
// is specified, the '0' flag is ignored."
|
||||
if (!precision_specified && conv.has_zero_flag()) {
|
||||
num_zeroes += num_left_spaces;
|
||||
num_left_spaces = 0;
|
||||
}
|
||||
|
||||
sink->Append(num_left_spaces, ' ');
|
||||
sink->Append(sign);
|
||||
sink->Append(base_indicator);
|
||||
sink->Append(num_zeroes, '0');
|
||||
sink->Append(formatted);
|
||||
sink->Append(num_right_spaces, ' ');
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool ConvertFloatArg(T v, FormatConversionSpecImpl conv, FormatSinkImpl *sink) {
|
||||
if (conv.conversion_char() == FormatConversionCharInternal::v) {
|
||||
conv.set_conversion_char(FormatConversionCharInternal::g);
|
||||
}
|
||||
|
||||
return FormatConversionCharIsFloat(conv.conversion_char()) &&
|
||||
ConvertFloatImpl(v, conv, sink);
|
||||
}
|
||||
|
||||
inline bool ConvertStringArg(string_view v, const FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink) {
|
||||
if (conv.is_basic()) {
|
||||
sink->Append(v);
|
||||
return true;
|
||||
}
|
||||
return sink->PutPaddedString(v, conv.width(), conv.precision(),
|
||||
conv.has_left_flag());
|
||||
}
|
||||
|
||||
struct ShiftState {
|
||||
bool saw_high_surrogate = false;
|
||||
uint8_t bits = 0;
|
||||
};
|
||||
|
||||
// Converts `v` from UTF-16 or UTF-32 to UTF-8 and writes to `buf`. `buf` is
|
||||
// assumed to have enough space for the output. `s` is used to carry state
|
||||
// between successive calls with a UTF-16 surrogate pair. Returns the number of
|
||||
// chars written, or `static_cast<size_t>(-1)` on failure.
|
||||
//
|
||||
// This is basically std::wcrtomb(), but always outputting UTF-8 instead of
|
||||
// respecting the current locale.
|
||||
inline size_t WideToUtf8(wchar_t wc, char *buf, ShiftState &s) {
|
||||
const auto v = static_cast<uint32_t>(wc);
|
||||
if (v < 0x80) {
|
||||
*buf = static_cast<char>(v);
|
||||
return 1;
|
||||
} else if (v < 0x800) {
|
||||
*buf++ = static_cast<char>(0xc0 | (v >> 6));
|
||||
*buf = static_cast<char>(0x80 | (v & 0x3f));
|
||||
return 2;
|
||||
} else if (v < 0xd800 || (v - 0xe000) < 0x2000) {
|
||||
*buf++ = static_cast<char>(0xe0 | (v >> 12));
|
||||
*buf++ = static_cast<char>(0x80 | ((v >> 6) & 0x3f));
|
||||
*buf = static_cast<char>(0x80 | (v & 0x3f));
|
||||
return 3;
|
||||
} else if ((v - 0x10000) < 0x100000) {
|
||||
*buf++ = static_cast<char>(0xf0 | (v >> 18));
|
||||
*buf++ = static_cast<char>(0x80 | ((v >> 12) & 0x3f));
|
||||
*buf++ = static_cast<char>(0x80 | ((v >> 6) & 0x3f));
|
||||
*buf = static_cast<char>(0x80 | (v & 0x3f));
|
||||
return 4;
|
||||
} else if (v < 0xdc00) {
|
||||
s.saw_high_surrogate = true;
|
||||
s.bits = static_cast<uint8_t>(v & 0x3);
|
||||
const uint8_t high_bits = ((v >> 6) & 0xf) + 1;
|
||||
*buf++ = static_cast<char>(0xf0 | (high_bits >> 2));
|
||||
*buf =
|
||||
static_cast<char>(0x80 | static_cast<uint8_t>((high_bits & 0x3) << 4) |
|
||||
static_cast<uint8_t>((v >> 2) & 0xf));
|
||||
return 2;
|
||||
} else if (v < 0xe000 && s.saw_high_surrogate) {
|
||||
*buf++ = static_cast<char>(0x80 | static_cast<uint8_t>(s.bits << 4) |
|
||||
static_cast<uint8_t>((v >> 6) & 0xf));
|
||||
*buf = static_cast<char>(0x80 | (v & 0x3f));
|
||||
s.saw_high_surrogate = false;
|
||||
s.bits = 0;
|
||||
return 2;
|
||||
} else {
|
||||
return static_cast<size_t>(-1);
|
||||
}
|
||||
}
|
||||
|
||||
inline bool ConvertStringArg(const wchar_t *v,
|
||||
size_t len,
|
||||
const FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink) {
|
||||
FixedArray<char> mb(len * 4);
|
||||
ShiftState s;
|
||||
size_t chars_written = 0;
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
const size_t chars = WideToUtf8(v[i], &mb[chars_written], s);
|
||||
if (chars == static_cast<size_t>(-1)) { return false; }
|
||||
chars_written += chars;
|
||||
}
|
||||
return ConvertStringArg(string_view(mb.data(), chars_written), conv, sink);
|
||||
}
|
||||
|
||||
bool ConvertWCharTImpl(wchar_t v, const FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink) {
|
||||
char mb[4];
|
||||
ShiftState s;
|
||||
const size_t chars_written = WideToUtf8(v, mb, s);
|
||||
return chars_written != static_cast<size_t>(-1) && !s.saw_high_surrogate &&
|
||||
ConvertStringArg(string_view(mb, chars_written), conv, sink);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool ConvertBoolArg(bool v, FormatSinkImpl *sink) {
|
||||
if (v) {
|
||||
sink->Append("true");
|
||||
} else {
|
||||
sink->Append("false");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool ConvertIntArg(T v, FormatConversionSpecImpl conv, FormatSinkImpl *sink) {
|
||||
using U = typename MakeUnsigned<T>::type;
|
||||
IntDigits as_digits;
|
||||
|
||||
// This odd casting is due to a bug in -Wswitch behavior in gcc49 which causes
|
||||
// it to complain about a switch/case type mismatch, even though both are
|
||||
// FormatConversionChar. Likely this is because at this point
|
||||
// FormatConversionChar is declared, but not defined.
|
||||
switch (static_cast<uint8_t>(conv.conversion_char())) {
|
||||
case static_cast<uint8_t>(FormatConversionCharInternal::c):
|
||||
return (std::is_same<T, wchar_t>::value ||
|
||||
(conv.length_mod() == LengthMod::l))
|
||||
? ConvertWCharTImpl(static_cast<wchar_t>(v), conv, sink)
|
||||
: ConvertCharImpl(static_cast<char>(v), conv, sink);
|
||||
|
||||
case static_cast<uint8_t>(FormatConversionCharInternal::o):
|
||||
as_digits.PrintAsOct(static_cast<U>(v));
|
||||
break;
|
||||
|
||||
case static_cast<uint8_t>(FormatConversionCharInternal::x):
|
||||
as_digits.PrintAsHexLower(static_cast<U>(v));
|
||||
break;
|
||||
case static_cast<uint8_t>(FormatConversionCharInternal::X):
|
||||
as_digits.PrintAsHexUpper(static_cast<U>(v));
|
||||
break;
|
||||
|
||||
case static_cast<uint8_t>(FormatConversionCharInternal::u):
|
||||
as_digits.PrintAsDec(static_cast<U>(v));
|
||||
break;
|
||||
|
||||
case static_cast<uint8_t>(FormatConversionCharInternal::d):
|
||||
case static_cast<uint8_t>(FormatConversionCharInternal::i):
|
||||
case static_cast<uint8_t>(FormatConversionCharInternal::v):
|
||||
as_digits.PrintAsDec(v);
|
||||
break;
|
||||
|
||||
case static_cast<uint8_t>(FormatConversionCharInternal::a):
|
||||
case static_cast<uint8_t>(FormatConversionCharInternal::e):
|
||||
case static_cast<uint8_t>(FormatConversionCharInternal::f):
|
||||
case static_cast<uint8_t>(FormatConversionCharInternal::g):
|
||||
case static_cast<uint8_t>(FormatConversionCharInternal::A):
|
||||
case static_cast<uint8_t>(FormatConversionCharInternal::E):
|
||||
case static_cast<uint8_t>(FormatConversionCharInternal::F):
|
||||
case static_cast<uint8_t>(FormatConversionCharInternal::G):
|
||||
return ConvertFloatImpl(static_cast<double>(v), conv, sink);
|
||||
|
||||
default:
|
||||
ABSL_ASSUME(false);
|
||||
}
|
||||
|
||||
if (conv.is_basic()) {
|
||||
sink->Append(as_digits.with_neg_and_zero());
|
||||
return true;
|
||||
}
|
||||
return ConvertIntImplInnerSlow(as_digits, conv, sink);
|
||||
}
|
||||
|
||||
template bool ConvertIntArg<char>(char v, FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink);
|
||||
template bool ConvertIntArg<signed char>(signed char v,
|
||||
FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink);
|
||||
template bool ConvertIntArg<unsigned char>(unsigned char v,
|
||||
FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink);
|
||||
template bool ConvertIntArg<wchar_t>(wchar_t v, FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink);
|
||||
template bool ConvertIntArg<short>(short v, // NOLINT
|
||||
FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink);
|
||||
template bool ConvertIntArg<unsigned short>(unsigned short v, // NOLINT
|
||||
FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink);
|
||||
template bool ConvertIntArg<int>(int v, FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink);
|
||||
template bool ConvertIntArg<unsigned int>(unsigned int v,
|
||||
FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink);
|
||||
template bool ConvertIntArg<long>(long v, // NOLINT
|
||||
FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink);
|
||||
template bool ConvertIntArg<unsigned long>(unsigned long v, // NOLINT
|
||||
FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink);
|
||||
template bool ConvertIntArg<long long>(long long v, // NOLINT
|
||||
FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink);
|
||||
template bool ConvertIntArg<unsigned long long>(unsigned long long v, // NOLINT
|
||||
FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink);
|
||||
|
||||
// ==================== Strings ====================
|
||||
StringConvertResult FormatConvertImpl(const std::string &v,
|
||||
const FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink) {
|
||||
return {ConvertStringArg(v, conv, sink)};
|
||||
}
|
||||
|
||||
StringConvertResult FormatConvertImpl(const std::wstring &v,
|
||||
const FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink) {
|
||||
return {ConvertStringArg(v.data(), v.size(), conv, sink)};
|
||||
}
|
||||
|
||||
StringConvertResult FormatConvertImpl(string_view v,
|
||||
const FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink) {
|
||||
return {ConvertStringArg(v, conv, sink)};
|
||||
}
|
||||
|
||||
#if defined(ABSL_HAVE_STD_STRING_VIEW)
|
||||
StringConvertResult FormatConvertImpl(std::wstring_view v,
|
||||
const FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink) {
|
||||
return {ConvertStringArg(v.data(), v.size(), conv, sink)};
|
||||
}
|
||||
#endif
|
||||
|
||||
StringPtrConvertResult FormatConvertImpl(const char* v,
|
||||
const FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink) {
|
||||
if (conv.conversion_char() == FormatConversionCharInternal::p)
|
||||
return {FormatConvertImpl(VoidPtr(v), conv, sink).value};
|
||||
size_t len;
|
||||
if (v == nullptr) {
|
||||
len = 0;
|
||||
} else if (conv.precision() < 0) {
|
||||
len = std::strlen(v);
|
||||
} else {
|
||||
// If precision is set, we look for the NUL-terminator on the valid range.
|
||||
len = static_cast<size_t>(std::find(v, v + conv.precision(), '\0') - v);
|
||||
}
|
||||
return {ConvertStringArg(string_view(v, len), conv, sink)};
|
||||
}
|
||||
|
||||
StringPtrConvertResult FormatConvertImpl(const wchar_t* v,
|
||||
const FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink) {
|
||||
if (conv.conversion_char() == FormatConversionCharInternal::p) {
|
||||
return {FormatConvertImpl(VoidPtr(v), conv, sink).value};
|
||||
}
|
||||
size_t len;
|
||||
if (v == nullptr) {
|
||||
len = 0;
|
||||
} else if (conv.precision() < 0) {
|
||||
len = std::wcslen(v);
|
||||
} else {
|
||||
// If precision is set, we look for the NUL-terminator on the valid range.
|
||||
len = static_cast<size_t>(std::find(v, v + conv.precision(), L'\0') - v);
|
||||
}
|
||||
return {ConvertStringArg(v, len, conv, sink)};
|
||||
}
|
||||
|
||||
StringPtrConvertResult FormatConvertImpl(std::nullptr_t,
|
||||
const FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink) {
|
||||
return FormatConvertImpl(static_cast<const char*>(nullptr), conv, sink);
|
||||
}
|
||||
|
||||
// ==================== Raw pointers ====================
|
||||
ArgConvertResult<FormatConversionCharSetInternal::p> FormatConvertImpl(
|
||||
VoidPtr v, const FormatConversionSpecImpl conv, FormatSinkImpl *sink) {
|
||||
if (!v.value) {
|
||||
sink->Append("(nil)");
|
||||
return {true};
|
||||
}
|
||||
IntDigits as_digits;
|
||||
as_digits.PrintAsHexLower(v.value);
|
||||
return {ConvertIntImplInnerSlow(as_digits, conv, sink)};
|
||||
}
|
||||
|
||||
// ==================== Floats ====================
|
||||
FloatingConvertResult FormatConvertImpl(float v,
|
||||
const FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink) {
|
||||
return {ConvertFloatArg(v, conv, sink)};
|
||||
}
|
||||
FloatingConvertResult FormatConvertImpl(double v,
|
||||
const FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink) {
|
||||
return {ConvertFloatArg(v, conv, sink)};
|
||||
}
|
||||
FloatingConvertResult FormatConvertImpl(long double v,
|
||||
const FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink) {
|
||||
return {ConvertFloatArg(v, conv, sink)};
|
||||
}
|
||||
|
||||
// ==================== Chars ====================
|
||||
CharConvertResult FormatConvertImpl(char v, const FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink) {
|
||||
return {ConvertIntArg(v, conv, sink)};
|
||||
}
|
||||
CharConvertResult FormatConvertImpl(wchar_t v,
|
||||
const FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink) {
|
||||
return {ConvertIntArg(v, conv, sink)};
|
||||
}
|
||||
|
||||
// ==================== Ints ====================
|
||||
IntegralConvertResult FormatConvertImpl(signed char v,
|
||||
const FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink) {
|
||||
return {ConvertIntArg(v, conv, sink)};
|
||||
}
|
||||
IntegralConvertResult FormatConvertImpl(unsigned char v,
|
||||
const FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink) {
|
||||
return {ConvertIntArg(v, conv, sink)};
|
||||
}
|
||||
IntegralConvertResult FormatConvertImpl(short v, // NOLINT
|
||||
const FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink) {
|
||||
return {ConvertIntArg(v, conv, sink)};
|
||||
}
|
||||
IntegralConvertResult FormatConvertImpl(unsigned short v, // NOLINT
|
||||
const FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink) {
|
||||
return {ConvertIntArg(v, conv, sink)};
|
||||
}
|
||||
IntegralConvertResult FormatConvertImpl(int v,
|
||||
const FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink) {
|
||||
return {ConvertIntArg(v, conv, sink)};
|
||||
}
|
||||
IntegralConvertResult FormatConvertImpl(unsigned v,
|
||||
const FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink) {
|
||||
return {ConvertIntArg(v, conv, sink)};
|
||||
}
|
||||
IntegralConvertResult FormatConvertImpl(long v, // NOLINT
|
||||
const FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink) {
|
||||
return {ConvertIntArg(v, conv, sink)};
|
||||
}
|
||||
IntegralConvertResult FormatConvertImpl(unsigned long v, // NOLINT
|
||||
const FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink) {
|
||||
return {ConvertIntArg(v, conv, sink)};
|
||||
}
|
||||
IntegralConvertResult FormatConvertImpl(long long v, // NOLINT
|
||||
const FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink) {
|
||||
return {ConvertIntArg(v, conv, sink)};
|
||||
}
|
||||
IntegralConvertResult FormatConvertImpl(unsigned long long v, // NOLINT
|
||||
const FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink) {
|
||||
return {ConvertIntArg(v, conv, sink)};
|
||||
}
|
||||
IntegralConvertResult FormatConvertImpl(absl::int128 v,
|
||||
const FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink) {
|
||||
return {ConvertIntArg(v, conv, sink)};
|
||||
}
|
||||
IntegralConvertResult FormatConvertImpl(absl::uint128 v,
|
||||
const FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl *sink) {
|
||||
return {ConvertIntArg(v, conv, sink)};
|
||||
}
|
||||
|
||||
ABSL_INTERNAL_FORMAT_DISPATCH_OVERLOADS_EXPAND_();
|
||||
|
||||
|
||||
|
||||
} // namespace str_format_internal
|
||||
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
671
Pods/abseil/absl/strings/internal/str_format/arg.h
generated
Normal file
671
Pods/abseil/absl/strings/internal/str_format/arg.h
generated
Normal file
@@ -0,0 +1,671 @@
|
||||
// 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_
|
||||
|
||||
#include <string.h>
|
||||
#include <wchar.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "absl/base/config.h"
|
||||
#include "absl/base/optimization.h"
|
||||
#include "absl/meta/type_traits.h"
|
||||
#include "absl/numeric/int128.h"
|
||||
#include "absl/strings/has_absl_stringify.h"
|
||||
#include "absl/strings/internal/str_format/extension.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
|
||||
#if defined(ABSL_HAVE_STD_STRING_VIEW)
|
||||
#include <string_view>
|
||||
#endif
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
|
||||
class Cord;
|
||||
class FormatCountCapture;
|
||||
class FormatSink;
|
||||
|
||||
template <absl::FormatConversionCharSet C>
|
||||
struct FormatConvertResult;
|
||||
class FormatConversionSpec;
|
||||
|
||||
namespace str_format_internal {
|
||||
|
||||
template <FormatConversionCharSet C>
|
||||
struct ArgConvertResult {
|
||||
bool value;
|
||||
};
|
||||
|
||||
using IntegralConvertResult = ArgConvertResult<FormatConversionCharSetUnion(
|
||||
FormatConversionCharSetInternal::c,
|
||||
FormatConversionCharSetInternal::kNumeric,
|
||||
FormatConversionCharSetInternal::kStar,
|
||||
FormatConversionCharSetInternal::v)>;
|
||||
using FloatingConvertResult = ArgConvertResult<FormatConversionCharSetUnion(
|
||||
FormatConversionCharSetInternal::kFloating,
|
||||
FormatConversionCharSetInternal::v)>;
|
||||
using CharConvertResult = ArgConvertResult<FormatConversionCharSetUnion(
|
||||
FormatConversionCharSetInternal::c,
|
||||
FormatConversionCharSetInternal::kNumeric,
|
||||
FormatConversionCharSetInternal::kStar)>;
|
||||
|
||||
template <typename T, typename = void>
|
||||
struct HasUserDefinedConvert : std::false_type {};
|
||||
|
||||
template <typename T>
|
||||
struct HasUserDefinedConvert<T, void_t<decltype(AbslFormatConvert(
|
||||
std::declval<const T&>(),
|
||||
std::declval<const FormatConversionSpec&>(),
|
||||
std::declval<FormatSink*>()))>>
|
||||
: std::true_type {};
|
||||
|
||||
// These declarations prevent ADL lookup from continuing in absl namespaces,
|
||||
// we are deliberately using these as ADL hooks and want them to consider
|
||||
// non-absl namespaces only.
|
||||
void AbslFormatConvert();
|
||||
void AbslStringify();
|
||||
|
||||
template <typename T>
|
||||
bool ConvertIntArg(T v, FormatConversionSpecImpl conv, FormatSinkImpl* sink);
|
||||
|
||||
// Forward declarations of internal `ConvertIntArg` function template
|
||||
// instantiations are here to avoid including the template body in the headers
|
||||
// and instantiating it in large numbers of translation units. Explicit
|
||||
// instantiations can be found in "absl/strings/internal/str_format/arg.cc"
|
||||
extern template bool ConvertIntArg<char>(char v, FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink);
|
||||
extern template bool ConvertIntArg<signed char>(signed char v,
|
||||
FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink);
|
||||
extern template bool ConvertIntArg<unsigned char>(unsigned char v,
|
||||
FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink);
|
||||
extern template bool ConvertIntArg<wchar_t>(wchar_t v,
|
||||
FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink);
|
||||
extern template bool ConvertIntArg<short>(short v, // NOLINT
|
||||
FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink);
|
||||
extern template bool ConvertIntArg<unsigned short>( // NOLINT
|
||||
unsigned short v, FormatConversionSpecImpl conv, // NOLINT
|
||||
FormatSinkImpl* sink);
|
||||
extern template bool ConvertIntArg<int>(int v, FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink);
|
||||
extern template bool ConvertIntArg<unsigned int>(unsigned int v,
|
||||
FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink);
|
||||
extern template bool ConvertIntArg<long>( // NOLINT
|
||||
long v, FormatConversionSpecImpl conv, FormatSinkImpl* sink); // NOLINT
|
||||
extern template bool ConvertIntArg<unsigned long>(unsigned long v, // NOLINT
|
||||
FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink);
|
||||
extern template bool ConvertIntArg<long long>(long long v, // NOLINT
|
||||
FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink);
|
||||
extern template bool ConvertIntArg<unsigned long long>( // NOLINT
|
||||
unsigned long long v, FormatConversionSpecImpl conv, // NOLINT
|
||||
FormatSinkImpl* sink);
|
||||
|
||||
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>
|
||||
auto FormatConvertImpl(const T& v, FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink)
|
||||
-> std::enable_if_t<std::is_enum<T>::value &&
|
||||
std::is_void<decltype(AbslStringify(
|
||||
std::declval<FormatSink&>(), v))>::value,
|
||||
IntegralConvertResult> {
|
||||
if (conv.conversion_char() == FormatConversionCharInternal::v) {
|
||||
using FormatSinkT =
|
||||
absl::enable_if_t<sizeof(const T& (*)()) != 0, FormatSink>;
|
||||
auto fs = sink->Wrap<FormatSinkT>();
|
||||
AbslStringify(fs, v);
|
||||
return {true};
|
||||
} else {
|
||||
return {ConvertIntArg(
|
||||
static_cast<typename std::underlying_type<T>::type>(v), conv, sink)};
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
auto FormatConvertImpl(const T& v, FormatConversionSpecImpl,
|
||||
FormatSinkImpl* sink)
|
||||
-> std::enable_if_t<!std::is_enum<T>::value &&
|
||||
!std::is_same<T, absl::Cord>::value &&
|
||||
std::is_void<decltype(AbslStringify(
|
||||
std::declval<FormatSink&>(), v))>::value,
|
||||
ArgConvertResult<FormatConversionCharSetInternal::v>> {
|
||||
using FormatSinkT =
|
||||
absl::enable_if_t<sizeof(const T& (*)()) != 0, FormatSink>;
|
||||
auto fs = sink->Wrap<FormatSinkT>();
|
||||
AbslStringify(fs, v);
|
||||
return {true};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
class StreamedWrapper;
|
||||
|
||||
// If 'v' can be converted (in the printf sense) according to 'conv',
|
||||
// then convert it, appending to `sink` and return `true`.
|
||||
// Otherwise fail and return `false`.
|
||||
|
||||
// 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;
|
||||
template <typename T,
|
||||
decltype(reinterpret_cast<uintptr_t>(std::declval<T*>())) = 0>
|
||||
VoidPtr(T* ptr) // NOLINT
|
||||
: value(ptr ? reinterpret_cast<uintptr_t>(ptr) : 0) {}
|
||||
uintptr_t value;
|
||||
};
|
||||
|
||||
template <FormatConversionCharSet C>
|
||||
constexpr FormatConversionCharSet ExtractCharSet(FormatConvertResult<C>) {
|
||||
return C;
|
||||
}
|
||||
|
||||
template <FormatConversionCharSet C>
|
||||
constexpr FormatConversionCharSet ExtractCharSet(ArgConvertResult<C>) {
|
||||
return C;
|
||||
}
|
||||
|
||||
ArgConvertResult<FormatConversionCharSetInternal::p> FormatConvertImpl(
|
||||
VoidPtr v, FormatConversionSpecImpl conv, FormatSinkImpl* sink);
|
||||
|
||||
// Strings.
|
||||
using StringConvertResult = ArgConvertResult<FormatConversionCharSetUnion(
|
||||
FormatConversionCharSetInternal::s,
|
||||
FormatConversionCharSetInternal::v)>;
|
||||
StringConvertResult FormatConvertImpl(const std::string& v,
|
||||
FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink);
|
||||
StringConvertResult FormatConvertImpl(const std::wstring& v,
|
||||
FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink);
|
||||
StringConvertResult FormatConvertImpl(string_view v,
|
||||
FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink);
|
||||
#if defined(ABSL_HAVE_STD_STRING_VIEW)
|
||||
StringConvertResult FormatConvertImpl(std::wstring_view v,
|
||||
FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink);
|
||||
#if !defined(ABSL_USES_STD_STRING_VIEW)
|
||||
inline StringConvertResult FormatConvertImpl(std::string_view v,
|
||||
FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink) {
|
||||
return FormatConvertImpl(absl::string_view(v.data(), v.size()), conv, sink);
|
||||
}
|
||||
#endif // !ABSL_USES_STD_STRING_VIEW
|
||||
#endif // ABSL_HAVE_STD_STRING_VIEW
|
||||
|
||||
using StringPtrConvertResult = ArgConvertResult<FormatConversionCharSetUnion(
|
||||
FormatConversionCharSetInternal::s,
|
||||
FormatConversionCharSetInternal::p)>;
|
||||
StringPtrConvertResult FormatConvertImpl(const char* v,
|
||||
FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink);
|
||||
StringPtrConvertResult FormatConvertImpl(const wchar_t* v,
|
||||
FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink);
|
||||
// This overload is needed to disambiguate, since `nullptr` could match either
|
||||
// of the other overloads equally well.
|
||||
StringPtrConvertResult FormatConvertImpl(std::nullptr_t,
|
||||
FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink);
|
||||
|
||||
template <class AbslCord, typename std::enable_if<std::is_same<
|
||||
AbslCord, absl::Cord>::value>::type* = nullptr>
|
||||
StringConvertResult FormatConvertImpl(const AbslCord& value,
|
||||
FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink) {
|
||||
bool is_left = conv.has_left_flag();
|
||||
size_t space_remaining = 0;
|
||||
|
||||
int width = conv.width();
|
||||
if (width >= 0) space_remaining = static_cast<size_t>(width);
|
||||
|
||||
size_t to_write = value.size();
|
||||
|
||||
int precision = conv.precision();
|
||||
if (precision >= 0)
|
||||
to_write = (std::min)(to_write, static_cast<size_t>(precision));
|
||||
|
||||
space_remaining = Excess(to_write, space_remaining);
|
||||
|
||||
if (space_remaining > 0 && !is_left) sink->Append(space_remaining, ' ');
|
||||
|
||||
for (string_view piece : value.Chunks()) {
|
||||
if (piece.size() > to_write) {
|
||||
piece.remove_suffix(piece.size() - to_write);
|
||||
to_write = 0;
|
||||
} else {
|
||||
to_write -= piece.size();
|
||||
}
|
||||
sink->Append(piece);
|
||||
if (to_write == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (space_remaining > 0 && is_left) sink->Append(space_remaining, ' ');
|
||||
return {true};
|
||||
}
|
||||
|
||||
bool ConvertBoolArg(bool v, FormatSinkImpl* sink);
|
||||
|
||||
// Floats.
|
||||
FloatingConvertResult FormatConvertImpl(float v, FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink);
|
||||
FloatingConvertResult FormatConvertImpl(double v, FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink);
|
||||
FloatingConvertResult FormatConvertImpl(long double v,
|
||||
FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink);
|
||||
|
||||
// Chars.
|
||||
CharConvertResult FormatConvertImpl(char v, FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink);
|
||||
CharConvertResult FormatConvertImpl(wchar_t v,
|
||||
FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink);
|
||||
|
||||
// Ints.
|
||||
IntegralConvertResult FormatConvertImpl(signed char v,
|
||||
FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink);
|
||||
IntegralConvertResult FormatConvertImpl(unsigned char v,
|
||||
FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink);
|
||||
IntegralConvertResult FormatConvertImpl(short v, // NOLINT
|
||||
FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink);
|
||||
IntegralConvertResult FormatConvertImpl(unsigned short v, // NOLINT
|
||||
FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink);
|
||||
IntegralConvertResult FormatConvertImpl(int v, FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink);
|
||||
IntegralConvertResult FormatConvertImpl(unsigned v,
|
||||
FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink);
|
||||
IntegralConvertResult FormatConvertImpl(long v, // NOLINT
|
||||
FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink);
|
||||
IntegralConvertResult FormatConvertImpl(unsigned long v, // NOLINT
|
||||
FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink);
|
||||
IntegralConvertResult FormatConvertImpl(long long v, // NOLINT
|
||||
FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink);
|
||||
IntegralConvertResult FormatConvertImpl(unsigned long long v, // NOLINT
|
||||
FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink);
|
||||
IntegralConvertResult FormatConvertImpl(int128 v, FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink);
|
||||
IntegralConvertResult FormatConvertImpl(uint128 v,
|
||||
FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink);
|
||||
|
||||
// This function needs to be a template due to ambiguity regarding type
|
||||
// conversions.
|
||||
template <typename T, enable_if_t<std::is_same<T, bool>::value, int> = 0>
|
||||
IntegralConvertResult FormatConvertImpl(T v, FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink) {
|
||||
if (conv.conversion_char() == FormatConversionCharInternal::v) {
|
||||
return {ConvertBoolArg(v, sink)};
|
||||
}
|
||||
|
||||
return FormatConvertImpl(static_cast<int>(v), conv, sink);
|
||||
}
|
||||
|
||||
// We provide this function to help the checker, but it is never defined.
|
||||
// FormatArgImpl will use the underlying Convert functions instead.
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_enum<T>::value &&
|
||||
!HasUserDefinedConvert<T>::value &&
|
||||
!HasAbslStringify<T>::value,
|
||||
IntegralConvertResult>::type
|
||||
FormatConvertImpl(T v, FormatConversionSpecImpl conv, FormatSinkImpl* sink);
|
||||
|
||||
template <typename T>
|
||||
StringConvertResult FormatConvertImpl(const StreamedWrapper<T>& v,
|
||||
FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* out) {
|
||||
std::ostringstream oss;
|
||||
oss << v.v_;
|
||||
if (!oss) return {false};
|
||||
return str_format_internal::FormatConvertImpl(oss.str(), conv, out);
|
||||
}
|
||||
|
||||
// Use templates and dependent types to delay evaluation of the function
|
||||
// until after FormatCountCapture is fully defined.
|
||||
struct FormatCountCaptureHelper {
|
||||
template <class T = int>
|
||||
static ArgConvertResult<FormatConversionCharSetInternal::n> ConvertHelper(
|
||||
const FormatCountCapture& v, FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink) {
|
||||
const absl::enable_if_t<sizeof(T) != 0, FormatCountCapture>& v2 = v;
|
||||
|
||||
if (conv.conversion_char() !=
|
||||
str_format_internal::FormatConversionCharInternal::n) {
|
||||
return {false};
|
||||
}
|
||||
*v2.p_ = static_cast<int>(sink->size());
|
||||
return {true};
|
||||
}
|
||||
};
|
||||
|
||||
template <class T = int>
|
||||
ArgConvertResult<FormatConversionCharSetInternal::n> FormatConvertImpl(
|
||||
const FormatCountCapture& v, FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink) {
|
||||
return FormatCountCaptureHelper::ConvertHelper(v, conv, sink);
|
||||
}
|
||||
|
||||
// Helper friend struct to hide implementation details from the public API of
|
||||
// FormatArgImpl.
|
||||
struct FormatArgImplFriend {
|
||||
template <typename Arg>
|
||||
static bool ToInt(Arg arg, int* out) {
|
||||
// A value initialized FormatConversionSpecImpl has a `none` conv, which
|
||||
// tells the dispatcher to run the `int` conversion.
|
||||
return arg.dispatcher_(arg.data_, {}, out);
|
||||
}
|
||||
|
||||
template <typename Arg>
|
||||
static bool Convert(Arg arg, FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* out) {
|
||||
return arg.dispatcher_(arg.data_, conv, out);
|
||||
}
|
||||
|
||||
template <typename Arg>
|
||||
static typename Arg::Dispatcher GetVTablePtrForTest(Arg arg) {
|
||||
return arg.dispatcher_;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Arg>
|
||||
constexpr FormatConversionCharSet ArgumentToConv() {
|
||||
using ConvResult = decltype(str_format_internal::FormatConvertImpl(
|
||||
std::declval<const Arg&>(),
|
||||
std::declval<const FormatConversionSpecImpl&>(),
|
||||
std::declval<FormatSinkImpl*>()));
|
||||
return absl::str_format_internal::ExtractCharSet(ConvResult{});
|
||||
}
|
||||
|
||||
// A type-erased handle to a format argument.
|
||||
class FormatArgImpl {
|
||||
private:
|
||||
enum { kInlinedSpace = 8 };
|
||||
|
||||
using VoidPtr = str_format_internal::VoidPtr;
|
||||
|
||||
union Data {
|
||||
const void* ptr;
|
||||
const volatile void* volatile_ptr;
|
||||
char buf[kInlinedSpace];
|
||||
};
|
||||
|
||||
using Dispatcher = bool (*)(Data, FormatConversionSpecImpl, void* out);
|
||||
|
||||
template <typename T>
|
||||
struct store_by_value
|
||||
: std::integral_constant<bool, (sizeof(T) <= kInlinedSpace) &&
|
||||
(std::is_integral<T>::value ||
|
||||
std::is_floating_point<T>::value ||
|
||||
std::is_pointer<T>::value ||
|
||||
std::is_same<VoidPtr, T>::value)> {};
|
||||
|
||||
enum StoragePolicy { ByPointer, ByVolatilePointer, ByValue };
|
||||
template <typename T>
|
||||
struct storage_policy
|
||||
: std::integral_constant<StoragePolicy,
|
||||
(std::is_volatile<T>::value
|
||||
? ByVolatilePointer
|
||||
: (store_by_value<T>::value ? ByValue
|
||||
: ByPointer))> {
|
||||
};
|
||||
|
||||
// To reduce the number of vtables we will decay values before hand.
|
||||
// Anything with a user-defined Convert will get its own vtable.
|
||||
// For everything else:
|
||||
// - Decay char* and char arrays into `const char*`
|
||||
// - Decay wchar_t* and wchar_t arrays into `const wchar_t*`
|
||||
// - Decay any other pointer to `const void*`
|
||||
// - Decay all enums to the integral promotion of their underlying type.
|
||||
// - Decay function pointers to void*.
|
||||
template <typename T, typename = void>
|
||||
struct DecayType {
|
||||
static constexpr bool kHasUserDefined =
|
||||
str_format_internal::HasUserDefinedConvert<T>::value ||
|
||||
HasAbslStringify<T>::value;
|
||||
using type = typename std::conditional<
|
||||
!kHasUserDefined && std::is_convertible<T, const char*>::value,
|
||||
const char*,
|
||||
typename std::conditional<
|
||||
!kHasUserDefined && std::is_convertible<T, const wchar_t*>::value,
|
||||
const wchar_t*,
|
||||
typename std::conditional<
|
||||
!kHasUserDefined && std::is_convertible<T, VoidPtr>::value,
|
||||
VoidPtr,
|
||||
const T&>::type>::type>::type;
|
||||
};
|
||||
template <typename T>
|
||||
struct DecayType<
|
||||
T, typename std::enable_if<
|
||||
!str_format_internal::HasUserDefinedConvert<T>::value &&
|
||||
!HasAbslStringify<T>::value && std::is_enum<T>::value>::type> {
|
||||
using type = decltype(+typename std::underlying_type<T>::type());
|
||||
};
|
||||
|
||||
public:
|
||||
template <typename T>
|
||||
explicit FormatArgImpl(const T& value) {
|
||||
using D = typename DecayType<T>::type;
|
||||
static_assert(
|
||||
std::is_same<D, const T&>::value || storage_policy<D>::value == ByValue,
|
||||
"Decayed types must be stored by value");
|
||||
Init(static_cast<D>(value));
|
||||
}
|
||||
|
||||
private:
|
||||
friend struct str_format_internal::FormatArgImplFriend;
|
||||
template <typename T, StoragePolicy = storage_policy<T>::value>
|
||||
struct Manager;
|
||||
|
||||
template <typename T>
|
||||
struct Manager<T, ByPointer> {
|
||||
static Data SetValue(const T& value) {
|
||||
Data data;
|
||||
data.ptr = std::addressof(value);
|
||||
return data;
|
||||
}
|
||||
|
||||
static const T& Value(Data arg) { return *static_cast<const T*>(arg.ptr); }
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct Manager<T, ByVolatilePointer> {
|
||||
static Data SetValue(const T& value) {
|
||||
Data data;
|
||||
data.volatile_ptr = &value;
|
||||
return data;
|
||||
}
|
||||
|
||||
static const T& Value(Data arg) {
|
||||
return *static_cast<const T*>(arg.volatile_ptr);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct Manager<T, ByValue> {
|
||||
static Data SetValue(const T& value) {
|
||||
Data data;
|
||||
memcpy(data.buf, &value, sizeof(value));
|
||||
return data;
|
||||
}
|
||||
|
||||
static T Value(Data arg) {
|
||||
T value;
|
||||
memcpy(&value, arg.buf, sizeof(T));
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
void Init(const T& value) {
|
||||
data_ = Manager<T>::SetValue(value);
|
||||
dispatcher_ = &Dispatch<T>;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static int ToIntVal(const T& val) {
|
||||
using CommonType = typename std::conditional<std::is_signed<T>::value,
|
||||
int64_t, uint64_t>::type;
|
||||
if (static_cast<CommonType>(val) >
|
||||
static_cast<CommonType>((std::numeric_limits<int>::max)())) {
|
||||
return (std::numeric_limits<int>::max)();
|
||||
} else if (std::is_signed<T>::value &&
|
||||
static_cast<CommonType>(val) <
|
||||
static_cast<CommonType>((std::numeric_limits<int>::min)())) {
|
||||
return (std::numeric_limits<int>::min)();
|
||||
}
|
||||
return static_cast<int>(val);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static bool ToInt(Data arg, int* out, std::true_type /* is_integral */,
|
||||
std::false_type) {
|
||||
*out = ToIntVal(Manager<T>::Value(arg));
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static bool ToInt(Data arg, int* out, std::false_type,
|
||||
std::true_type /* is_enum */) {
|
||||
*out = ToIntVal(static_cast<typename std::underlying_type<T>::type>(
|
||||
Manager<T>::Value(arg)));
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static bool ToInt(Data, int*, std::false_type, std::false_type) {
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static bool Dispatch(Data arg, FormatConversionSpecImpl spec, void* out) {
|
||||
// A `none` conv indicates that we want the `int` conversion.
|
||||
if (ABSL_PREDICT_FALSE(spec.conversion_char() ==
|
||||
FormatConversionCharInternal::kNone)) {
|
||||
return ToInt<T>(arg, static_cast<int*>(out), std::is_integral<T>(),
|
||||
std::is_enum<T>());
|
||||
}
|
||||
if (ABSL_PREDICT_FALSE(!Contains(ArgumentToConv<T>(),
|
||||
spec.conversion_char()))) {
|
||||
return false;
|
||||
}
|
||||
return str_format_internal::FormatConvertImpl(
|
||||
Manager<T>::Value(arg), spec,
|
||||
static_cast<FormatSinkImpl*>(out))
|
||||
.value;
|
||||
}
|
||||
|
||||
Data data_;
|
||||
Dispatcher dispatcher_;
|
||||
};
|
||||
|
||||
#define ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(T, E) \
|
||||
E template bool FormatArgImpl::Dispatch<T>(Data, FormatConversionSpecImpl, \
|
||||
void*)
|
||||
|
||||
#define ABSL_INTERNAL_FORMAT_DISPATCH_OVERLOADS_EXPAND_NO_WSTRING_VIEW_(...) \
|
||||
ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(str_format_internal::VoidPtr, \
|
||||
__VA_ARGS__); \
|
||||
ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(bool, __VA_ARGS__); \
|
||||
ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(char, __VA_ARGS__); \
|
||||
ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(signed char, __VA_ARGS__); \
|
||||
ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(unsigned char, __VA_ARGS__); \
|
||||
ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(short, __VA_ARGS__); /* NOLINT */ \
|
||||
ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(unsigned short, /* NOLINT */ \
|
||||
__VA_ARGS__); \
|
||||
ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(int, __VA_ARGS__); \
|
||||
ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(unsigned int, __VA_ARGS__); \
|
||||
ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(long, __VA_ARGS__); /* NOLINT */ \
|
||||
ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(unsigned long, /* NOLINT */ \
|
||||
__VA_ARGS__); \
|
||||
ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(long long, /* NOLINT */ \
|
||||
__VA_ARGS__); \
|
||||
ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(unsigned long long, /* NOLINT */ \
|
||||
__VA_ARGS__); \
|
||||
ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(int128, __VA_ARGS__); \
|
||||
ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(uint128, __VA_ARGS__); \
|
||||
ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(float, __VA_ARGS__); \
|
||||
ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(double, __VA_ARGS__); \
|
||||
ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(long double, __VA_ARGS__); \
|
||||
ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(const char*, __VA_ARGS__); \
|
||||
ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(std::string, __VA_ARGS__); \
|
||||
ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(string_view, __VA_ARGS__); \
|
||||
ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(const wchar_t*, __VA_ARGS__); \
|
||||
ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(std::wstring, __VA_ARGS__)
|
||||
|
||||
#if defined(ABSL_HAVE_STD_STRING_VIEW)
|
||||
#define ABSL_INTERNAL_FORMAT_DISPATCH_OVERLOADS_EXPAND_(...) \
|
||||
ABSL_INTERNAL_FORMAT_DISPATCH_OVERLOADS_EXPAND_NO_WSTRING_VIEW_( \
|
||||
__VA_ARGS__); \
|
||||
ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(std::wstring_view, __VA_ARGS__)
|
||||
#else
|
||||
#define ABSL_INTERNAL_FORMAT_DISPATCH_OVERLOADS_EXPAND_(...) \
|
||||
ABSL_INTERNAL_FORMAT_DISPATCH_OVERLOADS_EXPAND_NO_WSTRING_VIEW_(__VA_ARGS__)
|
||||
#endif
|
||||
|
||||
ABSL_INTERNAL_FORMAT_DISPATCH_OVERLOADS_EXPAND_(extern);
|
||||
|
||||
|
||||
} // namespace str_format_internal
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
|
||||
#endif // ABSL_STRINGS_INTERNAL_STR_FORMAT_ARG_H_
|
||||
275
Pods/abseil/absl/strings/internal/str_format/bind.cc
generated
Normal file
275
Pods/abseil/absl/strings/internal/str_format/bind.cc
generated
Normal file
@@ -0,0 +1,275 @@
|
||||
// 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 <algorithm>
|
||||
#include <cassert>
|
||||
#include <cerrno>
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <ios>
|
||||
#include <limits>
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include "absl/base/config.h"
|
||||
#include "absl/base/optimization.h"
|
||||
#include "absl/strings/internal/str_format/arg.h"
|
||||
#include "absl/strings/internal/str_format/constexpr_parser.h"
|
||||
#include "absl/strings/internal/str_format/extension.h"
|
||||
#include "absl/strings/internal/str_format/output.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "absl/types/span.h"
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace str_format_internal {
|
||||
|
||||
namespace {
|
||||
|
||||
inline bool BindFromPosition(int position, int* value,
|
||||
absl::Span<const FormatArgImpl> pack) {
|
||||
assert(position > 0);
|
||||
if (static_cast<size_t>(position) > pack.size()) {
|
||||
return false;
|
||||
}
|
||||
// -1 because positions are 1-based
|
||||
return FormatArgImplFriend::ToInt(pack[static_cast<size_t>(position) - 1],
|
||||
value);
|
||||
}
|
||||
|
||||
class ArgContext {
|
||||
public:
|
||||
explicit ArgContext(absl::Span<const FormatArgImpl> pack) : pack_(pack) {}
|
||||
|
||||
// Fill 'bound' with the results of applying the context's argument pack
|
||||
// to the specified 'unbound'. We synthesize a BoundConversion by
|
||||
// lining up a UnboundConversion with a user argument. We also
|
||||
// resolve any '*' specifiers for width and precision, so after
|
||||
// this call, 'bound' has all the information it needs to be formatted.
|
||||
// Returns false on failure.
|
||||
bool Bind(const UnboundConversion* unbound, BoundConversion* bound);
|
||||
|
||||
private:
|
||||
absl::Span<const FormatArgImpl> pack_;
|
||||
};
|
||||
|
||||
inline bool ArgContext::Bind(const UnboundConversion* unbound,
|
||||
BoundConversion* bound) {
|
||||
const FormatArgImpl* arg = nullptr;
|
||||
int arg_position = unbound->arg_position;
|
||||
if (static_cast<size_t>(arg_position - 1) >= pack_.size()) return false;
|
||||
arg = &pack_[static_cast<size_t>(arg_position - 1)]; // 1-based
|
||||
|
||||
if (unbound->flags != Flags::kBasic) {
|
||||
int width = unbound->width.value();
|
||||
bool force_left = false;
|
||||
if (unbound->width.is_from_arg()) {
|
||||
if (!BindFromPosition(unbound->width.get_from_arg(), &width, pack_))
|
||||
return false;
|
||||
if (width < 0) {
|
||||
// "A negative field width is taken as a '-' flag followed by a
|
||||
// positive field width."
|
||||
force_left = true;
|
||||
// Make sure we don't overflow the width when negating it.
|
||||
width = -std::max(width, -std::numeric_limits<int>::max());
|
||||
}
|
||||
}
|
||||
|
||||
int precision = unbound->precision.value();
|
||||
if (unbound->precision.is_from_arg()) {
|
||||
if (!BindFromPosition(unbound->precision.get_from_arg(), &precision,
|
||||
pack_))
|
||||
return false;
|
||||
}
|
||||
|
||||
FormatConversionSpecImplFriend::SetWidth(width, bound);
|
||||
FormatConversionSpecImplFriend::SetPrecision(precision, bound);
|
||||
|
||||
if (force_left) {
|
||||
FormatConversionSpecImplFriend::SetFlags(unbound->flags | Flags::kLeft,
|
||||
bound);
|
||||
} else {
|
||||
FormatConversionSpecImplFriend::SetFlags(unbound->flags, bound);
|
||||
}
|
||||
|
||||
FormatConversionSpecImplFriend::SetLengthMod(unbound->length_mod, bound);
|
||||
} else {
|
||||
FormatConversionSpecImplFriend::SetFlags(unbound->flags, bound);
|
||||
FormatConversionSpecImplFriend::SetWidth(-1, bound);
|
||||
FormatConversionSpecImplFriend::SetPrecision(-1, bound);
|
||||
}
|
||||
FormatConversionSpecImplFriend::SetConversionChar(unbound->conv, bound);
|
||||
bound->set_arg(arg);
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Converter>
|
||||
class ConverterConsumer {
|
||||
public:
|
||||
ConverterConsumer(Converter converter, absl::Span<const FormatArgImpl> pack)
|
||||
: converter_(converter), arg_context_(pack) {}
|
||||
|
||||
bool Append(string_view s) {
|
||||
converter_.Append(s);
|
||||
return true;
|
||||
}
|
||||
bool ConvertOne(const UnboundConversion& conv, string_view conv_string) {
|
||||
BoundConversion bound;
|
||||
if (!arg_context_.Bind(&conv, &bound)) return false;
|
||||
return converter_.ConvertOne(bound, conv_string);
|
||||
}
|
||||
|
||||
private:
|
||||
Converter converter_;
|
||||
ArgContext arg_context_;
|
||||
};
|
||||
|
||||
template <typename Converter>
|
||||
bool ConvertAll(const UntypedFormatSpecImpl format,
|
||||
absl::Span<const FormatArgImpl> args, Converter converter) {
|
||||
if (format.has_parsed_conversion()) {
|
||||
return format.parsed_conversion()->ProcessFormat(
|
||||
ConverterConsumer<Converter>(converter, args));
|
||||
} else {
|
||||
return ParseFormatString(format.str(),
|
||||
ConverterConsumer<Converter>(converter, args));
|
||||
}
|
||||
}
|
||||
|
||||
class DefaultConverter {
|
||||
public:
|
||||
explicit DefaultConverter(FormatSinkImpl* sink) : sink_(sink) {}
|
||||
|
||||
void Append(string_view s) const { sink_->Append(s); }
|
||||
|
||||
bool ConvertOne(const BoundConversion& bound, string_view /*conv*/) const {
|
||||
return FormatArgImplFriend::Convert(*bound.arg(), bound, sink_);
|
||||
}
|
||||
|
||||
private:
|
||||
FormatSinkImpl* sink_;
|
||||
};
|
||||
|
||||
class SummarizingConverter {
|
||||
public:
|
||||
explicit SummarizingConverter(FormatSinkImpl* sink) : sink_(sink) {}
|
||||
|
||||
void Append(string_view s) const { sink_->Append(s); }
|
||||
|
||||
bool ConvertOne(const BoundConversion& bound, string_view /*conv*/) const {
|
||||
UntypedFormatSpecImpl spec("%d");
|
||||
|
||||
std::ostringstream ss;
|
||||
ss << "{" << Streamable(spec, {*bound.arg()}) << ":"
|
||||
<< FormatConversionSpecImplFriend::FlagsToString(bound);
|
||||
if (bound.width() >= 0) ss << bound.width();
|
||||
if (bound.precision() >= 0) ss << "." << bound.precision();
|
||||
ss << bound.conversion_char() << "}";
|
||||
Append(ss.str());
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
FormatSinkImpl* sink_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
bool BindWithPack(const UnboundConversion* props,
|
||||
absl::Span<const FormatArgImpl> pack,
|
||||
BoundConversion* bound) {
|
||||
return ArgContext(pack).Bind(props, bound);
|
||||
}
|
||||
|
||||
std::string Summarize(const UntypedFormatSpecImpl format,
|
||||
absl::Span<const FormatArgImpl> args) {
|
||||
typedef SummarizingConverter Converter;
|
||||
std::string out;
|
||||
{
|
||||
// inner block to destroy sink before returning out. It ensures a last
|
||||
// flush.
|
||||
FormatSinkImpl sink(&out);
|
||||
if (!ConvertAll(format, args, Converter(&sink))) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
bool FormatUntyped(FormatRawSinkImpl raw_sink,
|
||||
const UntypedFormatSpecImpl format,
|
||||
absl::Span<const FormatArgImpl> args) {
|
||||
FormatSinkImpl sink(raw_sink);
|
||||
using Converter = DefaultConverter;
|
||||
return ConvertAll(format, args, Converter(&sink));
|
||||
}
|
||||
|
||||
std::ostream& Streamable::Print(std::ostream& os) const {
|
||||
if (!FormatUntyped(&os, format_, args_)) os.setstate(std::ios::failbit);
|
||||
return os;
|
||||
}
|
||||
|
||||
std::string& AppendPack(std::string* out, const UntypedFormatSpecImpl format,
|
||||
absl::Span<const FormatArgImpl> args) {
|
||||
size_t orig = out->size();
|
||||
if (ABSL_PREDICT_FALSE(!FormatUntyped(out, format, args))) {
|
||||
out->erase(orig);
|
||||
}
|
||||
return *out;
|
||||
}
|
||||
|
||||
std::string FormatPack(UntypedFormatSpecImpl format,
|
||||
absl::Span<const FormatArgImpl> args) {
|
||||
std::string out;
|
||||
if (ABSL_PREDICT_FALSE(!FormatUntyped(&out, format, args))) {
|
||||
out.clear();
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
int FprintF(std::FILE* output, const UntypedFormatSpecImpl format,
|
||||
absl::Span<const FormatArgImpl> args) {
|
||||
FILERawSink sink(output);
|
||||
if (!FormatUntyped(&sink, format, args)) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
if (sink.error()) {
|
||||
errno = sink.error();
|
||||
return -1;
|
||||
}
|
||||
if (sink.count() > static_cast<size_t>(std::numeric_limits<int>::max())) {
|
||||
errno = EFBIG;
|
||||
return -1;
|
||||
}
|
||||
return static_cast<int>(sink.count());
|
||||
}
|
||||
|
||||
int SnprintF(char* output, size_t size, const UntypedFormatSpecImpl format,
|
||||
absl::Span<const FormatArgImpl> args) {
|
||||
BufferRawSink sink(output, size ? size - 1 : 0);
|
||||
if (!FormatUntyped(&sink, format, args)) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
size_t total = sink.total_written();
|
||||
if (size) output[std::min(total, size - 1)] = 0;
|
||||
return static_cast<int>(total);
|
||||
}
|
||||
|
||||
} // namespace str_format_internal
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
237
Pods/abseil/absl/strings/internal/str_format/bind.h
generated
Normal file
237
Pods/abseil/absl/strings/internal/str_format/bind.h
generated
Normal file
@@ -0,0 +1,237 @@
|
||||
// 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_
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
#include "absl/base/config.h"
|
||||
#include "absl/container/inlined_vector.h"
|
||||
#include "absl/strings/internal/str_format/arg.h"
|
||||
#include "absl/strings/internal/str_format/checker.h"
|
||||
#include "absl/strings/internal/str_format/constexpr_parser.h"
|
||||
#include "absl/strings/internal/str_format/extension.h"
|
||||
#include "absl/strings/internal/str_format/parser.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "absl/types/span.h"
|
||||
#include "absl/utility/utility.h"
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
|
||||
class UntypedFormatSpec;
|
||||
|
||||
namespace str_format_internal {
|
||||
|
||||
class BoundConversion : public FormatConversionSpecImpl {
|
||||
public:
|
||||
const FormatArgImpl* arg() const { return arg_; }
|
||||
void set_arg(const FormatArgImpl* a) { arg_ = a; }
|
||||
|
||||
private:
|
||||
const FormatArgImpl* arg_;
|
||||
};
|
||||
|
||||
// This is the type-erased class that the implementation uses.
|
||||
class UntypedFormatSpecImpl {
|
||||
public:
|
||||
UntypedFormatSpecImpl() = delete;
|
||||
|
||||
explicit UntypedFormatSpecImpl(string_view s)
|
||||
: data_(s.data()), size_(s.size()) {}
|
||||
explicit UntypedFormatSpecImpl(
|
||||
const str_format_internal::ParsedFormatBase* pc)
|
||||
: data_(pc), size_(~size_t{}) {}
|
||||
|
||||
bool has_parsed_conversion() const { return size_ == ~size_t{}; }
|
||||
|
||||
string_view str() const {
|
||||
assert(!has_parsed_conversion());
|
||||
return string_view(static_cast<const char*>(data_), size_);
|
||||
}
|
||||
const str_format_internal::ParsedFormatBase* parsed_conversion() const {
|
||||
assert(has_parsed_conversion());
|
||||
return static_cast<const str_format_internal::ParsedFormatBase*>(data_);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static const UntypedFormatSpecImpl& Extract(const T& s) {
|
||||
return s.spec_;
|
||||
}
|
||||
|
||||
private:
|
||||
const void* data_;
|
||||
size_t size_;
|
||||
};
|
||||
|
||||
template <typename T, FormatConversionCharSet...>
|
||||
struct MakeDependent {
|
||||
using type = T;
|
||||
};
|
||||
|
||||
// Implicitly convertible from `const char*`, `string_view`, and the
|
||||
// `ExtendedParsedFormat` type. This abstraction allows all format functions to
|
||||
// operate on any without providing too many overloads.
|
||||
template <FormatConversionCharSet... Args>
|
||||
class FormatSpecTemplate
|
||||
: public MakeDependent<UntypedFormatSpec, Args...>::type {
|
||||
using Base = typename MakeDependent<UntypedFormatSpec, Args...>::type;
|
||||
|
||||
template <bool res>
|
||||
struct ErrorMaker {
|
||||
constexpr bool operator()(int) const { return res; }
|
||||
};
|
||||
|
||||
template <int i, int j>
|
||||
static constexpr bool CheckArity(ErrorMaker<true> SpecifierCount = {},
|
||||
ErrorMaker<i == j> ParametersPassed = {}) {
|
||||
static_assert(SpecifierCount(i) == ParametersPassed(j),
|
||||
"Number of arguments passed must match the number of "
|
||||
"conversion specifiers.");
|
||||
return true;
|
||||
}
|
||||
|
||||
template <FormatConversionCharSet specified, FormatConversionCharSet passed,
|
||||
int arg>
|
||||
static constexpr bool CheckMatch(
|
||||
ErrorMaker<Contains(specified, passed)> MismatchedArgumentNumber = {}) {
|
||||
static_assert(MismatchedArgumentNumber(arg),
|
||||
"Passed argument must match specified format.");
|
||||
return true;
|
||||
}
|
||||
|
||||
template <FormatConversionCharSet... C, size_t... I>
|
||||
static bool CheckMatches(absl::index_sequence<I...>) {
|
||||
bool res[] = {true, CheckMatch<Args, C, I + 1>()...};
|
||||
(void)res;
|
||||
return true;
|
||||
}
|
||||
|
||||
public:
|
||||
#ifdef ABSL_INTERNAL_ENABLE_FORMAT_CHECKER
|
||||
|
||||
// Honeypot overload for when the string is not constexpr.
|
||||
// We use the 'unavailable' attribute to give a better compiler error than
|
||||
// just 'method is deleted'.
|
||||
FormatSpecTemplate(...) // NOLINT
|
||||
__attribute__((unavailable("Format string is not constexpr.")));
|
||||
|
||||
// Honeypot overload for when the format is constexpr and invalid.
|
||||
// We use the 'unavailable' attribute to give a better compiler error than
|
||||
// just 'method is deleted'.
|
||||
// To avoid checking the format twice, we just check that the format is
|
||||
// constexpr. If it is valid, then the overload below will kick in.
|
||||
// We add the template here to make this overload have lower priority.
|
||||
template <typename = void>
|
||||
FormatSpecTemplate(const char* s) // NOLINT
|
||||
__attribute__((
|
||||
enable_if(str_format_internal::EnsureConstexpr(s), "constexpr trap"),
|
||||
unavailable(
|
||||
"Format specified does not match the arguments passed.")));
|
||||
|
||||
template <typename T = void>
|
||||
FormatSpecTemplate(string_view s) // NOLINT
|
||||
__attribute__((enable_if(str_format_internal::EnsureConstexpr(s),
|
||||
"constexpr trap")))
|
||||
: Base("to avoid noise in the compiler error") {
|
||||
static_assert(sizeof(T*) == 0,
|
||||
"Format specified does not match the arguments passed.");
|
||||
}
|
||||
|
||||
// Good format overload.
|
||||
FormatSpecTemplate(const char* s) // NOLINT
|
||||
__attribute__((enable_if(ValidFormatImpl<Args...>(s), "bad format trap")))
|
||||
: Base(s) {}
|
||||
|
||||
FormatSpecTemplate(string_view s) // NOLINT
|
||||
__attribute__((enable_if(ValidFormatImpl<Args...>(s), "bad format trap")))
|
||||
: Base(s) {}
|
||||
|
||||
#else // ABSL_INTERNAL_ENABLE_FORMAT_CHECKER
|
||||
|
||||
FormatSpecTemplate(const char* s) : Base(s) {} // NOLINT
|
||||
FormatSpecTemplate(string_view s) : Base(s) {} // NOLINT
|
||||
|
||||
#endif // ABSL_INTERNAL_ENABLE_FORMAT_CHECKER
|
||||
|
||||
template <FormatConversionCharSet... C>
|
||||
FormatSpecTemplate(const ExtendedParsedFormat<C...>& pc) // NOLINT
|
||||
: Base(&pc) {
|
||||
CheckArity<sizeof...(C), sizeof...(Args)>();
|
||||
CheckMatches<C...>(absl::make_index_sequence<sizeof...(C)>{});
|
||||
}
|
||||
};
|
||||
|
||||
class Streamable {
|
||||
public:
|
||||
Streamable(const UntypedFormatSpecImpl& format,
|
||||
absl::Span<const FormatArgImpl> args)
|
||||
: format_(format), args_(args.begin(), args.end()) {}
|
||||
|
||||
std::ostream& Print(std::ostream& os) const;
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& os, const Streamable& l) {
|
||||
return l.Print(os);
|
||||
}
|
||||
|
||||
private:
|
||||
const UntypedFormatSpecImpl& format_;
|
||||
absl::InlinedVector<FormatArgImpl, 4> args_;
|
||||
};
|
||||
|
||||
// for testing
|
||||
std::string Summarize(UntypedFormatSpecImpl format,
|
||||
absl::Span<const FormatArgImpl> args);
|
||||
bool BindWithPack(const UnboundConversion* props,
|
||||
absl::Span<const FormatArgImpl> pack, BoundConversion* bound);
|
||||
|
||||
bool FormatUntyped(FormatRawSinkImpl raw_sink, UntypedFormatSpecImpl format,
|
||||
absl::Span<const FormatArgImpl> args);
|
||||
|
||||
std::string& AppendPack(std::string* out, UntypedFormatSpecImpl format,
|
||||
absl::Span<const FormatArgImpl> args);
|
||||
|
||||
std::string FormatPack(UntypedFormatSpecImpl format,
|
||||
absl::Span<const FormatArgImpl> args);
|
||||
|
||||
int FprintF(std::FILE* output, UntypedFormatSpecImpl format,
|
||||
absl::Span<const FormatArgImpl> args);
|
||||
int SnprintF(char* output, size_t size, UntypedFormatSpecImpl format,
|
||||
absl::Span<const FormatArgImpl> args);
|
||||
|
||||
// Returned by Streamed(v). Converts via '%s' to the std::string created
|
||||
// by std::ostream << v.
|
||||
template <typename T>
|
||||
class StreamedWrapper {
|
||||
public:
|
||||
explicit StreamedWrapper(const T& v) : v_(v) {}
|
||||
|
||||
private:
|
||||
template <typename S>
|
||||
friend ArgConvertResult<FormatConversionCharSetUnion(
|
||||
FormatConversionCharSetInternal::s, FormatConversionCharSetInternal::v)>
|
||||
FormatConvertImpl(const StreamedWrapper<S>& v, FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* out);
|
||||
const T& v_;
|
||||
};
|
||||
|
||||
} // namespace str_format_internal
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
|
||||
#endif // ABSL_STRINGS_INTERNAL_STR_FORMAT_BIND_H_
|
||||
100
Pods/abseil/absl/strings/internal/str_format/checker.h
generated
Normal file
100
Pods/abseil/absl/strings/internal/str_format/checker.h
generated
Normal file
@@ -0,0 +1,100 @@
|
||||
// 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_
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "absl/base/attributes.h"
|
||||
#include "absl/strings/internal/str_format/arg.h"
|
||||
#include "absl/strings/internal/str_format/constexpr_parser.h"
|
||||
#include "absl/strings/internal/str_format/extension.h"
|
||||
|
||||
// Compile time check support for entry points.
|
||||
|
||||
#ifndef ABSL_INTERNAL_ENABLE_FORMAT_CHECKER
|
||||
// We disable format checker under vscode intellisense compilation.
|
||||
// See https://github.com/microsoft/vscode-cpptools/issues/3683 for
|
||||
// more details.
|
||||
#if ABSL_HAVE_ATTRIBUTE(enable_if) && !defined(__native_client__) && \
|
||||
!defined(__INTELLISENSE__)
|
||||
#define ABSL_INTERNAL_ENABLE_FORMAT_CHECKER 1
|
||||
#endif // ABSL_HAVE_ATTRIBUTE(enable_if) && !defined(__native_client__) &&
|
||||
// !defined(__INTELLISENSE__)
|
||||
#endif // ABSL_INTERNAL_ENABLE_FORMAT_CHECKER
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace str_format_internal {
|
||||
|
||||
#ifdef ABSL_INTERNAL_ENABLE_FORMAT_CHECKER
|
||||
|
||||
template <FormatConversionCharSet... C>
|
||||
constexpr bool ValidFormatImpl(string_view format) {
|
||||
int next_arg = 0;
|
||||
const char* p = format.data();
|
||||
const char* const end = p + format.size();
|
||||
constexpr FormatConversionCharSet
|
||||
kAllowedConvs[(std::max)(sizeof...(C), size_t{1})] = {C...};
|
||||
bool used[(std::max)(sizeof...(C), size_t{1})]{};
|
||||
constexpr int kNumArgs = sizeof...(C);
|
||||
while (p != end) {
|
||||
while (p != end && *p != '%') ++p;
|
||||
if (p == end) {
|
||||
break;
|
||||
}
|
||||
if (p + 1 >= end) return false;
|
||||
if (p[1] == '%') {
|
||||
// %%
|
||||
p += 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
UnboundConversion conv(absl::kConstInit);
|
||||
p = ConsumeUnboundConversion(p + 1, end, &conv, &next_arg);
|
||||
if (p == nullptr) return false;
|
||||
if (conv.arg_position <= 0 || conv.arg_position > kNumArgs) {
|
||||
return false;
|
||||
}
|
||||
if (!Contains(kAllowedConvs[conv.arg_position - 1], conv.conv)) {
|
||||
return false;
|
||||
}
|
||||
used[conv.arg_position - 1] = true;
|
||||
for (auto extra : {conv.width, conv.precision}) {
|
||||
if (extra.is_from_arg()) {
|
||||
int pos = extra.get_from_arg();
|
||||
if (pos <= 0 || pos > kNumArgs) return false;
|
||||
used[pos - 1] = true;
|
||||
if (!Contains(kAllowedConvs[pos - 1], '*')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sizeof...(C) != 0) {
|
||||
for (bool b : used) {
|
||||
if (!b) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif // ABSL_INTERNAL_ENABLE_FORMAT_CHECKER
|
||||
|
||||
} // namespace str_format_internal
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
|
||||
#endif // ABSL_STRINGS_INTERNAL_STR_FORMAT_CHECKER_H_
|
||||
357
Pods/abseil/absl/strings/internal/str_format/constexpr_parser.h
generated
Normal file
357
Pods/abseil/absl/strings/internal/str_format/constexpr_parser.h
generated
Normal file
@@ -0,0 +1,357 @@
|
||||
// Copyright 2022 The Abseil Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef ABSL_STRINGS_INTERNAL_STR_FORMAT_CONSTEXPR_PARSER_H_
|
||||
#define ABSL_STRINGS_INTERNAL_STR_FORMAT_CONSTEXPR_PARSER_H_
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <limits>
|
||||
|
||||
#include "absl/base/config.h"
|
||||
#include "absl/base/const_init.h"
|
||||
#include "absl/base/optimization.h"
|
||||
#include "absl/strings/internal/str_format/extension.h"
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace str_format_internal {
|
||||
|
||||
// The analyzed properties of a single specified conversion.
|
||||
struct UnboundConversion {
|
||||
// This is a user defined default constructor on purpose to skip the
|
||||
// initialization of parts of the object that are not necessary.
|
||||
UnboundConversion() {} // NOLINT
|
||||
|
||||
// This constructor is provided for the static checker. We don't want to do
|
||||
// the unnecessary initialization in the normal case.
|
||||
explicit constexpr UnboundConversion(absl::ConstInitType)
|
||||
: arg_position{}, width{}, precision{} {}
|
||||
|
||||
class InputValue {
|
||||
public:
|
||||
constexpr void set_value(int value) {
|
||||
assert(value >= 0);
|
||||
value_ = value;
|
||||
}
|
||||
constexpr int value() const { return value_; }
|
||||
|
||||
// Marks the value as "from arg". aka the '*' format.
|
||||
// Requires `value >= 1`.
|
||||
// When set, is_from_arg() return true and get_from_arg() returns the
|
||||
// original value.
|
||||
// `value()`'s return value is unspecified in this state.
|
||||
constexpr void set_from_arg(int value) {
|
||||
assert(value > 0);
|
||||
value_ = -value - 1;
|
||||
}
|
||||
constexpr bool is_from_arg() const { return value_ < -1; }
|
||||
constexpr int get_from_arg() const {
|
||||
assert(is_from_arg());
|
||||
return -value_ - 1;
|
||||
}
|
||||
|
||||
private:
|
||||
int value_ = -1;
|
||||
};
|
||||
|
||||
// No need to initialize. It will always be set in the parser.
|
||||
int arg_position;
|
||||
|
||||
InputValue width;
|
||||
InputValue precision;
|
||||
|
||||
Flags flags = Flags::kBasic;
|
||||
LengthMod length_mod = LengthMod::none;
|
||||
FormatConversionChar conv = FormatConversionCharInternal::kNone;
|
||||
};
|
||||
|
||||
// Helper tag class for the table below.
|
||||
// It allows fast `char -> ConversionChar/LengthMod/Flags` checking and
|
||||
// conversions.
|
||||
class ConvTag {
|
||||
public:
|
||||
constexpr ConvTag(FormatConversionChar conversion_char) // NOLINT
|
||||
: tag_(static_cast<uint8_t>(conversion_char)) {}
|
||||
constexpr ConvTag(LengthMod length_mod) // NOLINT
|
||||
: tag_(0x80 | static_cast<uint8_t>(length_mod)) {}
|
||||
constexpr ConvTag(Flags flags) // NOLINT
|
||||
: tag_(0xc0 | static_cast<uint8_t>(flags)) {}
|
||||
constexpr ConvTag() : tag_(0xFF) {}
|
||||
|
||||
constexpr bool is_conv() const { return (tag_ & 0x80) == 0; }
|
||||
constexpr bool is_length() const { return (tag_ & 0xC0) == 0x80; }
|
||||
constexpr bool is_flags() const { return (tag_ & 0xE0) == 0xC0; }
|
||||
|
||||
constexpr FormatConversionChar as_conv() const {
|
||||
assert(is_conv());
|
||||
assert(!is_length());
|
||||
assert(!is_flags());
|
||||
return static_cast<FormatConversionChar>(tag_);
|
||||
}
|
||||
constexpr LengthMod as_length() const {
|
||||
assert(!is_conv());
|
||||
assert(is_length());
|
||||
assert(!is_flags());
|
||||
return static_cast<LengthMod>(tag_ & 0x3F);
|
||||
}
|
||||
constexpr Flags as_flags() const {
|
||||
assert(!is_conv());
|
||||
assert(!is_length());
|
||||
assert(is_flags());
|
||||
return static_cast<Flags>(tag_ & 0x1F);
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t tag_;
|
||||
};
|
||||
|
||||
struct ConvTagHolder {
|
||||
using CC = FormatConversionCharInternal;
|
||||
using LM = LengthMod;
|
||||
|
||||
// Abbreviations to fit in the table below.
|
||||
static constexpr auto kFSign = Flags::kSignCol;
|
||||
static constexpr auto kFAlt = Flags::kAlt;
|
||||
static constexpr auto kFPos = Flags::kShowPos;
|
||||
static constexpr auto kFLeft = Flags::kLeft;
|
||||
static constexpr auto kFZero = Flags::kZero;
|
||||
|
||||
static constexpr ConvTag value[256] = {
|
||||
{}, {}, {}, {}, {}, {}, {}, {}, // 00-07
|
||||
{}, {}, {}, {}, {}, {}, {}, {}, // 08-0f
|
||||
{}, {}, {}, {}, {}, {}, {}, {}, // 10-17
|
||||
{}, {}, {}, {}, {}, {}, {}, {}, // 18-1f
|
||||
kFSign, {}, {}, kFAlt, {}, {}, {}, {}, // !"#$%&'
|
||||
{}, {}, {}, kFPos, {}, kFLeft, {}, {}, // ()*+,-./
|
||||
kFZero, {}, {}, {}, {}, {}, {}, {}, // 01234567
|
||||
{}, {}, {}, {}, {}, {}, {}, {}, // 89:;<=>?
|
||||
{}, CC::A, {}, {}, {}, CC::E, CC::F, CC::G, // @ABCDEFG
|
||||
{}, {}, {}, {}, LM::L, {}, {}, {}, // HIJKLMNO
|
||||
{}, {}, {}, {}, {}, {}, {}, {}, // PQRSTUVW
|
||||
CC::X, {}, {}, {}, {}, {}, {}, {}, // XYZ[\]^_
|
||||
{}, CC::a, {}, CC::c, CC::d, CC::e, CC::f, CC::g, // `abcdefg
|
||||
LM::h, CC::i, LM::j, {}, LM::l, {}, CC::n, CC::o, // hijklmno
|
||||
CC::p, LM::q, {}, CC::s, LM::t, CC::u, CC::v, {}, // pqrstuvw
|
||||
CC::x, {}, LM::z, {}, {}, {}, {}, {}, // xyz{|}!
|
||||
{}, {}, {}, {}, {}, {}, {}, {}, // 80-87
|
||||
{}, {}, {}, {}, {}, {}, {}, {}, // 88-8f
|
||||
{}, {}, {}, {}, {}, {}, {}, {}, // 90-97
|
||||
{}, {}, {}, {}, {}, {}, {}, {}, // 98-9f
|
||||
{}, {}, {}, {}, {}, {}, {}, {}, // a0-a7
|
||||
{}, {}, {}, {}, {}, {}, {}, {}, // a8-af
|
||||
{}, {}, {}, {}, {}, {}, {}, {}, // b0-b7
|
||||
{}, {}, {}, {}, {}, {}, {}, {}, // b8-bf
|
||||
{}, {}, {}, {}, {}, {}, {}, {}, // c0-c7
|
||||
{}, {}, {}, {}, {}, {}, {}, {}, // c8-cf
|
||||
{}, {}, {}, {}, {}, {}, {}, {}, // d0-d7
|
||||
{}, {}, {}, {}, {}, {}, {}, {}, // d8-df
|
||||
{}, {}, {}, {}, {}, {}, {}, {}, // e0-e7
|
||||
{}, {}, {}, {}, {}, {}, {}, {}, // e8-ef
|
||||
{}, {}, {}, {}, {}, {}, {}, {}, // f0-f7
|
||||
{}, {}, {}, {}, {}, {}, {}, {}, // f8-ff
|
||||
};
|
||||
};
|
||||
|
||||
// Keep a single table for all the conversion chars and length modifiers.
|
||||
constexpr ConvTag GetTagForChar(char c) {
|
||||
return ConvTagHolder::value[static_cast<unsigned char>(c)];
|
||||
}
|
||||
|
||||
constexpr bool CheckFastPathSetting(const UnboundConversion& conv) {
|
||||
bool width_precision_needed =
|
||||
conv.width.value() >= 0 || conv.precision.value() >= 0;
|
||||
if (width_precision_needed && conv.flags == Flags::kBasic) {
|
||||
#if defined(__clang__)
|
||||
// Some compilers complain about this in constexpr even when not executed,
|
||||
// so only enable the error dump in clang.
|
||||
fprintf(stderr,
|
||||
"basic=%d left=%d show_pos=%d sign_col=%d alt=%d zero=%d "
|
||||
"width=%d precision=%d\n",
|
||||
conv.flags == Flags::kBasic ? 1 : 0,
|
||||
FlagsContains(conv.flags, Flags::kLeft) ? 1 : 0,
|
||||
FlagsContains(conv.flags, Flags::kShowPos) ? 1 : 0,
|
||||
FlagsContains(conv.flags, Flags::kSignCol) ? 1 : 0,
|
||||
FlagsContains(conv.flags, Flags::kAlt) ? 1 : 0,
|
||||
FlagsContains(conv.flags, Flags::kZero) ? 1 : 0, conv.width.value(),
|
||||
conv.precision.value());
|
||||
#endif // defined(__clang__)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
constexpr int ParseDigits(char& c, const char*& pos, const char* const end) {
|
||||
int digits = c - '0';
|
||||
// We do not want to overflow `digits` so we consume at most digits10
|
||||
// digits. If there are more digits the parsing will fail later on when the
|
||||
// digit doesn't match the expected characters.
|
||||
int num_digits = std::numeric_limits<int>::digits10;
|
||||
for (;;) {
|
||||
if (ABSL_PREDICT_FALSE(pos == end)) break;
|
||||
c = *pos++;
|
||||
if ('0' > c || c > '9') break;
|
||||
--num_digits;
|
||||
if (ABSL_PREDICT_FALSE(!num_digits)) break;
|
||||
digits = 10 * digits + c - '0';
|
||||
}
|
||||
return digits;
|
||||
}
|
||||
|
||||
template <bool is_positional>
|
||||
constexpr const char* ConsumeConversion(const char* pos, const char* const end,
|
||||
UnboundConversion* conv,
|
||||
int* next_arg) {
|
||||
const char* const original_pos = pos;
|
||||
char c = 0;
|
||||
// Read the next char into `c` and update `pos`. Returns false if there are
|
||||
// no more chars to read.
|
||||
#define ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR() \
|
||||
do { \
|
||||
if (ABSL_PREDICT_FALSE(pos == end)) return nullptr; \
|
||||
c = *pos++; \
|
||||
} while (0)
|
||||
|
||||
if (is_positional) {
|
||||
ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR();
|
||||
if (ABSL_PREDICT_FALSE(c < '1' || c > '9')) return nullptr;
|
||||
conv->arg_position = ParseDigits(c, pos, end);
|
||||
assert(conv->arg_position > 0);
|
||||
if (ABSL_PREDICT_FALSE(c != '$')) return nullptr;
|
||||
}
|
||||
|
||||
ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR();
|
||||
|
||||
// We should start with the basic flag on.
|
||||
assert(conv->flags == Flags::kBasic);
|
||||
|
||||
// Any non alpha character makes this conversion not basic.
|
||||
// This includes flags (-+ #0), width (1-9, *) or precision (.).
|
||||
// All conversion characters and length modifiers are alpha characters.
|
||||
if (c < 'A') {
|
||||
while (c <= '0') {
|
||||
auto tag = GetTagForChar(c);
|
||||
if (tag.is_flags()) {
|
||||
conv->flags = conv->flags | tag.as_flags();
|
||||
ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (c <= '9') {
|
||||
if (c >= '0') {
|
||||
int maybe_width = ParseDigits(c, pos, end);
|
||||
if (!is_positional && c == '$') {
|
||||
if (ABSL_PREDICT_FALSE(*next_arg != 0)) return nullptr;
|
||||
// Positional conversion.
|
||||
*next_arg = -1;
|
||||
return ConsumeConversion<true>(original_pos, end, conv, next_arg);
|
||||
}
|
||||
conv->flags = conv->flags | Flags::kNonBasic;
|
||||
conv->width.set_value(maybe_width);
|
||||
} else if (c == '*') {
|
||||
conv->flags = conv->flags | Flags::kNonBasic;
|
||||
ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR();
|
||||
if (is_positional) {
|
||||
if (ABSL_PREDICT_FALSE(c < '1' || c > '9')) return nullptr;
|
||||
conv->width.set_from_arg(ParseDigits(c, pos, end));
|
||||
if (ABSL_PREDICT_FALSE(c != '$')) return nullptr;
|
||||
ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR();
|
||||
} else {
|
||||
conv->width.set_from_arg(++*next_arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (c == '.') {
|
||||
conv->flags = conv->flags | Flags::kNonBasic;
|
||||
ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR();
|
||||
if ('0' <= c && c <= '9') {
|
||||
conv->precision.set_value(ParseDigits(c, pos, end));
|
||||
} else if (c == '*') {
|
||||
ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR();
|
||||
if (is_positional) {
|
||||
if (ABSL_PREDICT_FALSE(c < '1' || c > '9')) return nullptr;
|
||||
conv->precision.set_from_arg(ParseDigits(c, pos, end));
|
||||
if (c != '$') return nullptr;
|
||||
ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR();
|
||||
} else {
|
||||
conv->precision.set_from_arg(++*next_arg);
|
||||
}
|
||||
} else {
|
||||
conv->precision.set_value(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto tag = GetTagForChar(c);
|
||||
|
||||
if (ABSL_PREDICT_FALSE(c == 'v' && conv->flags != Flags::kBasic)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (ABSL_PREDICT_FALSE(!tag.is_conv())) {
|
||||
if (ABSL_PREDICT_FALSE(!tag.is_length())) return nullptr;
|
||||
|
||||
// It is a length modifier.
|
||||
LengthMod length_mod = tag.as_length();
|
||||
ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR();
|
||||
if (c == 'h' && length_mod == LengthMod::h) {
|
||||
conv->length_mod = LengthMod::hh;
|
||||
ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR();
|
||||
} else if (c == 'l' && length_mod == LengthMod::l) {
|
||||
conv->length_mod = LengthMod::ll;
|
||||
ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR();
|
||||
} else {
|
||||
conv->length_mod = length_mod;
|
||||
}
|
||||
tag = GetTagForChar(c);
|
||||
|
||||
if (ABSL_PREDICT_FALSE(c == 'v')) return nullptr;
|
||||
if (ABSL_PREDICT_FALSE(!tag.is_conv())) return nullptr;
|
||||
|
||||
// `wchar_t` args are marked non-basic so `Bind()` will copy the length mod.
|
||||
if (conv->length_mod == LengthMod::l && c == 'c') {
|
||||
conv->flags = conv->flags | Flags::kNonBasic;
|
||||
}
|
||||
}
|
||||
#undef ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR
|
||||
|
||||
assert(CheckFastPathSetting(*conv));
|
||||
(void)(&CheckFastPathSetting);
|
||||
|
||||
conv->conv = tag.as_conv();
|
||||
if (!is_positional) conv->arg_position = ++*next_arg;
|
||||
return pos;
|
||||
}
|
||||
|
||||
// Consume conversion spec prefix (not including '%') of [p, end) if valid.
|
||||
// Examples of valid specs would be e.g.: "s", "d", "-12.6f".
|
||||
// If valid, it returns the first character following the conversion spec,
|
||||
// and the spec part is broken down and returned in 'conv'.
|
||||
// If invalid, returns nullptr.
|
||||
constexpr const char* ConsumeUnboundConversion(const char* p, const char* end,
|
||||
UnboundConversion* conv,
|
||||
int* next_arg) {
|
||||
if (*next_arg < 0) return ConsumeConversion<true>(p, end, conv, next_arg);
|
||||
return ConsumeConversion<false>(p, end, conv, next_arg);
|
||||
}
|
||||
|
||||
} // namespace str_format_internal
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
|
||||
#endif // ABSL_STRINGS_INTERNAL_STR_FORMAT_CONSTEXPR_PARSER_H_
|
||||
75
Pods/abseil/absl/strings/internal/str_format/extension.cc
generated
Normal file
75
Pods/abseil/absl/strings/internal/str_format/extension.cc
generated
Normal file
@@ -0,0 +1,75 @@
|
||||
//
|
||||
// Copyright 2017 The Abseil Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// 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/extension.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace str_format_internal {
|
||||
|
||||
std::string FlagsToString(Flags v) {
|
||||
std::string s;
|
||||
s.append(FlagsContains(v, Flags::kLeft) ? "-" : "");
|
||||
s.append(FlagsContains(v, Flags::kShowPos) ? "+" : "");
|
||||
s.append(FlagsContains(v, Flags::kSignCol) ? " " : "");
|
||||
s.append(FlagsContains(v, Flags::kAlt) ? "#" : "");
|
||||
s.append(FlagsContains(v, Flags::kZero) ? "0" : "");
|
||||
return s;
|
||||
}
|
||||
|
||||
#ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL
|
||||
|
||||
#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
|
||||
|
||||
constexpr FormatConversionCharSet FormatConversionCharSetInternal::kStar;
|
||||
constexpr FormatConversionCharSet FormatConversionCharSetInternal::kIntegral;
|
||||
constexpr FormatConversionCharSet FormatConversionCharSetInternal::kFloating;
|
||||
constexpr FormatConversionCharSet FormatConversionCharSetInternal::kNumeric;
|
||||
constexpr FormatConversionCharSet FormatConversionCharSetInternal::kPointer;
|
||||
|
||||
#endif // ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL
|
||||
|
||||
bool FormatSinkImpl::PutPaddedString(string_view value, int width,
|
||||
int precision, bool left) {
|
||||
size_t space_remaining = 0;
|
||||
if (width >= 0)
|
||||
space_remaining = static_cast<size_t>(width);
|
||||
size_t n = value.size();
|
||||
if (precision >= 0) n = std::min(n, static_cast<size_t>(precision));
|
||||
string_view shown(value.data(), n);
|
||||
space_remaining = Excess(shown.size(), space_remaining);
|
||||
if (!left) Append(space_remaining, ' ');
|
||||
Append(shown);
|
||||
if (left) Append(space_remaining, ' ');
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace str_format_internal
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
456
Pods/abseil/absl/strings/internal/str_format/extension.h
generated
Normal file
456
Pods/abseil/absl/strings/internal/str_format/extension.h
generated
Normal file
@@ -0,0 +1,456 @@
|
||||
//
|
||||
// Copyright 2017 The Abseil Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// 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_EXTENSION_H_
|
||||
#define ABSL_STRINGS_INTERNAL_STR_FORMAT_EXTENSION_H_
|
||||
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
#include "absl/base/config.h"
|
||||
#include "absl/strings/internal/str_format/output.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
|
||||
enum class FormatConversionChar : uint8_t;
|
||||
enum class FormatConversionCharSet : uint64_t;
|
||||
enum class LengthMod : std::uint8_t { h, hh, l, ll, L, j, z, t, q, none };
|
||||
|
||||
namespace str_format_internal {
|
||||
|
||||
class FormatRawSinkImpl {
|
||||
public:
|
||||
// Implicitly convert from any type that provides the hook function as
|
||||
// described above.
|
||||
template <typename T, decltype(str_format_internal::InvokeFlush(
|
||||
std::declval<T*>(), string_view()))* = nullptr>
|
||||
FormatRawSinkImpl(T* raw) // NOLINT
|
||||
: sink_(raw), write_(&FormatRawSinkImpl::Flush<T>) {}
|
||||
|
||||
void Write(string_view s) { write_(sink_, s); }
|
||||
|
||||
template <typename T>
|
||||
static FormatRawSinkImpl Extract(T s) {
|
||||
return s.sink_;
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename T>
|
||||
static void Flush(void* r, string_view s) {
|
||||
str_format_internal::InvokeFlush(static_cast<T*>(r), s);
|
||||
}
|
||||
|
||||
void* sink_;
|
||||
void (*write_)(void*, string_view);
|
||||
};
|
||||
|
||||
// An abstraction to which conversions write their string data.
|
||||
class FormatSinkImpl {
|
||||
public:
|
||||
explicit FormatSinkImpl(FormatRawSinkImpl raw) : raw_(raw) {}
|
||||
|
||||
~FormatSinkImpl() { Flush(); }
|
||||
|
||||
void Flush() {
|
||||
raw_.Write(string_view(buf_, static_cast<size_t>(pos_ - buf_)));
|
||||
pos_ = buf_;
|
||||
}
|
||||
|
||||
void Append(size_t n, char c) {
|
||||
if (n == 0) return;
|
||||
size_ += n;
|
||||
auto raw_append = [&](size_t count) {
|
||||
memset(pos_, c, count);
|
||||
pos_ += count;
|
||||
};
|
||||
while (n > Avail()) {
|
||||
n -= Avail();
|
||||
if (Avail() > 0) {
|
||||
raw_append(Avail());
|
||||
}
|
||||
Flush();
|
||||
}
|
||||
raw_append(n);
|
||||
}
|
||||
|
||||
void Append(string_view v) {
|
||||
size_t n = v.size();
|
||||
if (n == 0) return;
|
||||
size_ += n;
|
||||
if (n >= Avail()) {
|
||||
Flush();
|
||||
raw_.Write(v);
|
||||
return;
|
||||
}
|
||||
memcpy(pos_, v.data(), n);
|
||||
pos_ += n;
|
||||
}
|
||||
|
||||
size_t size() const { return size_; }
|
||||
|
||||
// Put 'v' to 'sink' with specified width, precision, and left flag.
|
||||
bool PutPaddedString(string_view v, int width, int precision, bool left);
|
||||
|
||||
template <typename T>
|
||||
T Wrap() {
|
||||
return T(this);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static FormatSinkImpl* Extract(T* s) {
|
||||
return s->sink_;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t Avail() const {
|
||||
return static_cast<size_t>(buf_ + sizeof(buf_) - pos_);
|
||||
}
|
||||
|
||||
FormatRawSinkImpl raw_;
|
||||
size_t size_ = 0;
|
||||
char* pos_ = buf_;
|
||||
char buf_[1024];
|
||||
};
|
||||
|
||||
enum class Flags : uint8_t {
|
||||
kBasic = 0,
|
||||
kLeft = 1 << 0,
|
||||
kShowPos = 1 << 1,
|
||||
kSignCol = 1 << 2,
|
||||
kAlt = 1 << 3,
|
||||
kZero = 1 << 4,
|
||||
// This is not a real flag. It just exists to turn off kBasic when no other
|
||||
// flags are set. This is for when width/precision are specified, or a length
|
||||
// modifier affects the behavior ("%lc").
|
||||
kNonBasic = 1 << 5,
|
||||
};
|
||||
|
||||
constexpr Flags operator|(Flags a, Flags b) {
|
||||
return static_cast<Flags>(static_cast<uint8_t>(a) | static_cast<uint8_t>(b));
|
||||
}
|
||||
|
||||
constexpr bool FlagsContains(Flags haystack, Flags needle) {
|
||||
return (static_cast<uint8_t>(haystack) & static_cast<uint8_t>(needle)) ==
|
||||
static_cast<uint8_t>(needle);
|
||||
}
|
||||
|
||||
std::string FlagsToString(Flags v);
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& os, Flags v) {
|
||||
return os << FlagsToString(v);
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
#define ABSL_INTERNAL_CONVERSION_CHARS_EXPAND_(X_VAL, X_SEP) \
|
||||
/* text */ \
|
||||
X_VAL(c) X_SEP X_VAL(s) X_SEP \
|
||||
/* ints */ \
|
||||
X_VAL(d) X_SEP X_VAL(i) X_SEP X_VAL(o) X_SEP \
|
||||
X_VAL(u) X_SEP X_VAL(x) X_SEP X_VAL(X) X_SEP \
|
||||
/* floats */ \
|
||||
X_VAL(f) X_SEP X_VAL(F) X_SEP X_VAL(e) X_SEP X_VAL(E) X_SEP \
|
||||
X_VAL(g) X_SEP X_VAL(G) X_SEP X_VAL(a) X_SEP X_VAL(A) X_SEP \
|
||||
/* misc */ \
|
||||
X_VAL(n) X_SEP X_VAL(p) X_SEP X_VAL(v)
|
||||
// clang-format on
|
||||
|
||||
// This type should not be referenced, it exists only to provide labels
|
||||
// internally that match the values declared in FormatConversionChar in
|
||||
// str_format.h. This is meant to allow internal libraries to use the same
|
||||
// declared interface type as the public interface
|
||||
// (absl::StrFormatConversionChar) while keeping the definition in a public
|
||||
// header.
|
||||
// Internal libraries should use the form
|
||||
// `FormatConversionCharInternal::c`, `FormatConversionCharInternal::kNone` for
|
||||
// comparisons. Use in switch statements is not recommended due to a bug in how
|
||||
// gcc 4.9 -Wswitch handles declared but undefined enums.
|
||||
struct FormatConversionCharInternal {
|
||||
FormatConversionCharInternal() = delete;
|
||||
|
||||
private:
|
||||
// clang-format off
|
||||
enum class Enum : uint8_t {
|
||||
c, s, // text
|
||||
d, i, o, u, x, X, // int
|
||||
f, F, e, E, g, G, a, A, // float
|
||||
n, p, v, // misc
|
||||
kNone
|
||||
};
|
||||
// clang-format on
|
||||
public:
|
||||
#define ABSL_INTERNAL_X_VAL(id) \
|
||||
static constexpr FormatConversionChar id = \
|
||||
static_cast<FormatConversionChar>(Enum::id);
|
||||
ABSL_INTERNAL_CONVERSION_CHARS_EXPAND_(ABSL_INTERNAL_X_VAL, )
|
||||
#undef ABSL_INTERNAL_X_VAL
|
||||
static constexpr FormatConversionChar kNone =
|
||||
static_cast<FormatConversionChar>(Enum::kNone);
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
inline FormatConversionChar FormatConversionCharFromChar(char c) {
|
||||
switch (c) {
|
||||
#define ABSL_INTERNAL_X_VAL(id) \
|
||||
case #id[0]: \
|
||||
return FormatConversionCharInternal::id;
|
||||
ABSL_INTERNAL_CONVERSION_CHARS_EXPAND_(ABSL_INTERNAL_X_VAL, )
|
||||
#undef ABSL_INTERNAL_X_VAL
|
||||
}
|
||||
return FormatConversionCharInternal::kNone;
|
||||
}
|
||||
|
||||
inline bool FormatConversionCharIsUpper(FormatConversionChar c) {
|
||||
if (c == FormatConversionCharInternal::X ||
|
||||
c == FormatConversionCharInternal::F ||
|
||||
c == FormatConversionCharInternal::E ||
|
||||
c == FormatConversionCharInternal::G ||
|
||||
c == FormatConversionCharInternal::A) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
inline bool FormatConversionCharIsFloat(FormatConversionChar c) {
|
||||
if (c == FormatConversionCharInternal::a ||
|
||||
c == FormatConversionCharInternal::e ||
|
||||
c == FormatConversionCharInternal::f ||
|
||||
c == FormatConversionCharInternal::g ||
|
||||
c == FormatConversionCharInternal::A ||
|
||||
c == FormatConversionCharInternal::E ||
|
||||
c == FormatConversionCharInternal::F ||
|
||||
c == FormatConversionCharInternal::G) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
inline char FormatConversionCharToChar(FormatConversionChar c) {
|
||||
if (c == FormatConversionCharInternal::kNone) {
|
||||
return '\0';
|
||||
|
||||
#define ABSL_INTERNAL_X_VAL(e) \
|
||||
} else if (c == FormatConversionCharInternal::e) { \
|
||||
return #e[0];
|
||||
#define ABSL_INTERNAL_X_SEP
|
||||
ABSL_INTERNAL_CONVERSION_CHARS_EXPAND_(ABSL_INTERNAL_X_VAL,
|
||||
ABSL_INTERNAL_X_SEP)
|
||||
} else {
|
||||
return '\0';
|
||||
}
|
||||
|
||||
#undef ABSL_INTERNAL_X_VAL
|
||||
#undef ABSL_INTERNAL_X_SEP
|
||||
}
|
||||
|
||||
// The associated char.
|
||||
inline std::ostream& operator<<(std::ostream& os, FormatConversionChar v) {
|
||||
char c = FormatConversionCharToChar(v);
|
||||
if (!c) c = '?';
|
||||
return os << c;
|
||||
}
|
||||
|
||||
struct FormatConversionSpecImplFriend;
|
||||
|
||||
class FormatConversionSpecImpl {
|
||||
public:
|
||||
// Width and precision are not specified, no flags are set.
|
||||
bool is_basic() const { return flags_ == Flags::kBasic; }
|
||||
bool has_left_flag() const { return FlagsContains(flags_, Flags::kLeft); }
|
||||
bool has_show_pos_flag() const {
|
||||
return FlagsContains(flags_, Flags::kShowPos);
|
||||
}
|
||||
bool has_sign_col_flag() const {
|
||||
return FlagsContains(flags_, Flags::kSignCol);
|
||||
}
|
||||
bool has_alt_flag() const { return FlagsContains(flags_, Flags::kAlt); }
|
||||
bool has_zero_flag() const { return FlagsContains(flags_, Flags::kZero); }
|
||||
|
||||
LengthMod length_mod() const { return length_mod_; }
|
||||
|
||||
FormatConversionChar conversion_char() const {
|
||||
// Keep this field first in the struct . It generates better code when
|
||||
// accessing it when ConversionSpec is passed by value in registers.
|
||||
static_assert(offsetof(FormatConversionSpecImpl, conv_) == 0, "");
|
||||
return conv_;
|
||||
}
|
||||
|
||||
void set_conversion_char(FormatConversionChar c) { conv_ = c; }
|
||||
|
||||
// Returns the specified width. If width is unspecfied, it returns a negative
|
||||
// value.
|
||||
int width() const { return width_; }
|
||||
// Returns the specified precision. If precision is unspecfied, it returns a
|
||||
// negative value.
|
||||
int precision() const { return precision_; }
|
||||
|
||||
template <typename T>
|
||||
T Wrap() {
|
||||
return T(*this);
|
||||
}
|
||||
|
||||
private:
|
||||
friend struct str_format_internal::FormatConversionSpecImplFriend;
|
||||
FormatConversionChar conv_ = FormatConversionCharInternal::kNone;
|
||||
Flags flags_;
|
||||
LengthMod length_mod_ = LengthMod::none;
|
||||
int width_;
|
||||
int precision_;
|
||||
};
|
||||
|
||||
struct FormatConversionSpecImplFriend final {
|
||||
static void SetFlags(Flags f, FormatConversionSpecImpl* conv) {
|
||||
conv->flags_ = f;
|
||||
}
|
||||
static void SetLengthMod(LengthMod l, FormatConversionSpecImpl* conv) {
|
||||
conv->length_mod_ = l;
|
||||
}
|
||||
static void SetConversionChar(FormatConversionChar c,
|
||||
FormatConversionSpecImpl* conv) {
|
||||
conv->conv_ = c;
|
||||
}
|
||||
static void SetWidth(int w, FormatConversionSpecImpl* conv) {
|
||||
conv->width_ = w;
|
||||
}
|
||||
static void SetPrecision(int p, FormatConversionSpecImpl* conv) {
|
||||
conv->precision_ = p;
|
||||
}
|
||||
static std::string FlagsToString(const FormatConversionSpecImpl& spec) {
|
||||
return str_format_internal::FlagsToString(spec.flags_);
|
||||
}
|
||||
};
|
||||
|
||||
// Type safe OR operator.
|
||||
// We need this for two reasons:
|
||||
// 1. operator| on enums makes them decay to integers and the result is an
|
||||
// integer. We need the result to stay as an enum.
|
||||
// 2. We use "enum class" which would not work even if we accepted the decay.
|
||||
constexpr FormatConversionCharSet FormatConversionCharSetUnion(
|
||||
FormatConversionCharSet a) {
|
||||
return a;
|
||||
}
|
||||
|
||||
template <typename... CharSet>
|
||||
constexpr FormatConversionCharSet FormatConversionCharSetUnion(
|
||||
FormatConversionCharSet a, CharSet... rest) {
|
||||
return static_cast<FormatConversionCharSet>(
|
||||
static_cast<uint64_t>(a) |
|
||||
static_cast<uint64_t>(FormatConversionCharSetUnion(rest...)));
|
||||
}
|
||||
|
||||
constexpr uint64_t FormatConversionCharToConvInt(FormatConversionChar c) {
|
||||
return uint64_t{1} << (1 + static_cast<uint8_t>(c));
|
||||
}
|
||||
|
||||
constexpr uint64_t FormatConversionCharToConvInt(char conv) {
|
||||
return
|
||||
#define ABSL_INTERNAL_CHAR_SET_CASE(c) \
|
||||
conv == #c[0] \
|
||||
? FormatConversionCharToConvInt(FormatConversionCharInternal::c) \
|
||||
:
|
||||
ABSL_INTERNAL_CONVERSION_CHARS_EXPAND_(ABSL_INTERNAL_CHAR_SET_CASE, )
|
||||
#undef ABSL_INTERNAL_CHAR_SET_CASE
|
||||
conv == '*'
|
||||
? 1
|
||||
: 0;
|
||||
}
|
||||
|
||||
constexpr FormatConversionCharSet FormatConversionCharToConvValue(char conv) {
|
||||
return static_cast<FormatConversionCharSet>(
|
||||
FormatConversionCharToConvInt(conv));
|
||||
}
|
||||
|
||||
struct FormatConversionCharSetInternal {
|
||||
#define ABSL_INTERNAL_CHAR_SET_CASE(c) \
|
||||
static constexpr FormatConversionCharSet c = \
|
||||
FormatConversionCharToConvValue(#c[0]);
|
||||
ABSL_INTERNAL_CONVERSION_CHARS_EXPAND_(ABSL_INTERNAL_CHAR_SET_CASE, )
|
||||
#undef ABSL_INTERNAL_CHAR_SET_CASE
|
||||
|
||||
// Used for width/precision '*' specification.
|
||||
static constexpr FormatConversionCharSet kStar =
|
||||
FormatConversionCharToConvValue('*');
|
||||
|
||||
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 kPointer = p;
|
||||
};
|
||||
|
||||
// Type safe OR operator.
|
||||
// We need this for two reasons:
|
||||
// 1. operator| on enums makes them decay to integers and the result is an
|
||||
// integer. We need the result to stay as an enum.
|
||||
// 2. We use "enum class" which would not work even if we accepted the decay.
|
||||
constexpr FormatConversionCharSet operator|(FormatConversionCharSet a,
|
||||
FormatConversionCharSet b) {
|
||||
return FormatConversionCharSetUnion(a, b);
|
||||
}
|
||||
|
||||
// Overloaded conversion functions to support absl::ParsedFormat.
|
||||
// Get a conversion with a single character in it.
|
||||
constexpr FormatConversionCharSet ToFormatConversionCharSet(char c) {
|
||||
return static_cast<FormatConversionCharSet>(
|
||||
FormatConversionCharToConvValue(c));
|
||||
}
|
||||
|
||||
// Get a conversion with a single character in it.
|
||||
constexpr FormatConversionCharSet ToFormatConversionCharSet(
|
||||
FormatConversionCharSet c) {
|
||||
return c;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void ToFormatConversionCharSet(T) = delete;
|
||||
|
||||
// Checks whether `c` exists in `set`.
|
||||
constexpr bool Contains(FormatConversionCharSet set, char c) {
|
||||
return (static_cast<uint64_t>(set) &
|
||||
static_cast<uint64_t>(FormatConversionCharToConvValue(c))) != 0;
|
||||
}
|
||||
|
||||
// Checks whether all the characters in `c` are contained in `set`
|
||||
constexpr bool Contains(FormatConversionCharSet set,
|
||||
FormatConversionCharSet c) {
|
||||
return (static_cast<uint64_t>(set) & static_cast<uint64_t>(c)) ==
|
||||
static_cast<uint64_t>(c);
|
||||
}
|
||||
|
||||
// Checks whether all the characters in `c` are contained in `set`
|
||||
constexpr bool Contains(FormatConversionCharSet set, FormatConversionChar c) {
|
||||
return (static_cast<uint64_t>(set) & FormatConversionCharToConvInt(c)) != 0;
|
||||
}
|
||||
|
||||
// Return capacity - used, clipped to a minimum of 0.
|
||||
inline size_t Excess(size_t used, size_t capacity) {
|
||||
return used < capacity ? capacity - used : 0;
|
||||
}
|
||||
|
||||
} // namespace str_format_internal
|
||||
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
|
||||
#endif // ABSL_STRINGS_INTERNAL_STR_FORMAT_EXTENSION_H_
|
||||
1457
Pods/abseil/absl/strings/internal/str_format/float_conversion.cc
generated
Normal file
1457
Pods/abseil/absl/strings/internal/str_format/float_conversion.cc
generated
Normal file
@@ -0,0 +1,1457 @@
|
||||
// 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>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
|
||||
#include "absl/base/attributes.h"
|
||||
#include "absl/base/config.h"
|
||||
#include "absl/base/optimization.h"
|
||||
#include "absl/functional/function_ref.h"
|
||||
#include "absl/meta/type_traits.h"
|
||||
#include "absl/numeric/bits.h"
|
||||
#include "absl/numeric/int128.h"
|
||||
#include "absl/numeric/internal/representation.h"
|
||||
#include "absl/strings/numbers.h"
|
||||
#include "absl/types/optional.h"
|
||||
#include "absl/types/span.h"
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace str_format_internal {
|
||||
|
||||
namespace {
|
||||
|
||||
using ::absl::numeric_internal::IsDoubleDouble;
|
||||
|
||||
// The code below wants to avoid heap allocations.
|
||||
// To do so it needs to allocate memory on the stack.
|
||||
// `StackArray` will allocate memory on the stack in the form of a uint32_t
|
||||
// array and call the provided callback with said memory.
|
||||
// It will allocate memory in increments of 512 bytes. We could allocate the
|
||||
// largest needed unconditionally, but that is more than we need in most of
|
||||
// cases. This way we use less stack in the common cases.
|
||||
class StackArray {
|
||||
using Func = absl::FunctionRef<void(absl::Span<uint32_t>)>;
|
||||
static constexpr size_t kStep = 512 / sizeof(uint32_t);
|
||||
// 5 steps is 2560 bytes, which is enough to hold a long double with the
|
||||
// largest/smallest exponents.
|
||||
// The operations below will static_assert their particular maximum.
|
||||
static constexpr size_t kNumSteps = 5;
|
||||
|
||||
// We do not want this function to be inlined.
|
||||
// Otherwise the caller will allocate the stack space unnecessarily for all
|
||||
// the variants even though it only calls one.
|
||||
template <size_t steps>
|
||||
ABSL_ATTRIBUTE_NOINLINE static void RunWithCapacityImpl(Func f) {
|
||||
uint32_t values[steps * kStep]{};
|
||||
f(absl::MakeSpan(values));
|
||||
}
|
||||
|
||||
public:
|
||||
static constexpr size_t kMaxCapacity = kStep * kNumSteps;
|
||||
|
||||
static void RunWithCapacity(size_t capacity, Func f) {
|
||||
assert(capacity <= kMaxCapacity);
|
||||
const size_t step = (capacity + kStep - 1) / kStep;
|
||||
assert(step <= kNumSteps);
|
||||
switch (step) {
|
||||
case 1:
|
||||
return RunWithCapacityImpl<1>(f);
|
||||
case 2:
|
||||
return RunWithCapacityImpl<2>(f);
|
||||
case 3:
|
||||
return RunWithCapacityImpl<3>(f);
|
||||
case 4:
|
||||
return RunWithCapacityImpl<4>(f);
|
||||
case 5:
|
||||
return RunWithCapacityImpl<5>(f);
|
||||
}
|
||||
|
||||
assert(false && "Invalid capacity");
|
||||
}
|
||||
};
|
||||
|
||||
// Calculates `10 * (*v) + carry` and stores the result in `*v` and returns
|
||||
// the carry.
|
||||
// Requires: `0 <= carry <= 9`
|
||||
template <typename Int>
|
||||
inline char MultiplyBy10WithCarry(Int* v, char carry) {
|
||||
using BiggerInt = absl::conditional_t<sizeof(Int) == 4, uint64_t, uint128>;
|
||||
BiggerInt tmp =
|
||||
10 * static_cast<BiggerInt>(*v) + static_cast<BiggerInt>(carry);
|
||||
*v = static_cast<Int>(tmp);
|
||||
return static_cast<char>(tmp >> (sizeof(Int) * 8));
|
||||
}
|
||||
|
||||
// Calculates `(2^64 * carry + *v) / 10`.
|
||||
// Stores the quotient in `*v` and returns the remainder.
|
||||
// Requires: `0 <= carry <= 9`
|
||||
inline char DivideBy10WithCarry(uint64_t* v, char carry) {
|
||||
constexpr uint64_t divisor = 10;
|
||||
// 2^64 / divisor = chunk_quotient + chunk_remainder / divisor
|
||||
constexpr uint64_t chunk_quotient = (uint64_t{1} << 63) / (divisor / 2);
|
||||
constexpr uint64_t chunk_remainder = uint64_t{} - chunk_quotient * divisor;
|
||||
|
||||
const uint64_t carry_u64 = static_cast<uint64_t>(carry);
|
||||
const uint64_t mod = *v % divisor;
|
||||
const uint64_t next_carry = chunk_remainder * carry_u64 + mod;
|
||||
*v = *v / divisor + carry_u64 * chunk_quotient + next_carry / divisor;
|
||||
return static_cast<char>(next_carry % divisor);
|
||||
}
|
||||
|
||||
using MaxFloatType =
|
||||
typename std::conditional<IsDoubleDouble(), double, long double>::type;
|
||||
|
||||
// Generates the decimal representation for an integer of the form `v * 2^exp`,
|
||||
// where `v` and `exp` are both positive integers.
|
||||
// It generates the digits from the left (ie the most significant digit first)
|
||||
// to allow for direct printing into the sink.
|
||||
//
|
||||
// Requires `0 <= exp` and `exp <= numeric_limits<MaxFloatType>::max_exponent`.
|
||||
class BinaryToDecimal {
|
||||
static constexpr size_t ChunksNeeded(int exp) {
|
||||
// We will left shift a uint128 by `exp` bits, so we need `128+exp` total
|
||||
// bits. Round up to 32.
|
||||
// See constructor for details about adding `10%` to the value.
|
||||
return static_cast<size_t>((128 + exp + 31) / 32 * 11 / 10);
|
||||
}
|
||||
|
||||
public:
|
||||
// Run the conversion for `v * 2^exp` and call `f(binary_to_decimal)`.
|
||||
// This function will allocate enough stack space to perform the conversion.
|
||||
static void RunConversion(uint128 v, int exp,
|
||||
absl::FunctionRef<void(BinaryToDecimal)> f) {
|
||||
assert(exp > 0);
|
||||
assert(exp <= std::numeric_limits<MaxFloatType>::max_exponent);
|
||||
static_assert(
|
||||
StackArray::kMaxCapacity >=
|
||||
ChunksNeeded(std::numeric_limits<MaxFloatType>::max_exponent),
|
||||
"");
|
||||
|
||||
StackArray::RunWithCapacity(
|
||||
ChunksNeeded(exp),
|
||||
[=](absl::Span<uint32_t> input) { f(BinaryToDecimal(input, v, exp)); });
|
||||
}
|
||||
|
||||
size_t TotalDigits() const {
|
||||
return (decimal_end_ - decimal_start_) * kDigitsPerChunk +
|
||||
CurrentDigits().size();
|
||||
}
|
||||
|
||||
// See the current block of digits.
|
||||
absl::string_view CurrentDigits() const {
|
||||
return absl::string_view(digits_ + kDigitsPerChunk - size_, size_);
|
||||
}
|
||||
|
||||
// Advance the current view of digits.
|
||||
// Returns `false` when no more digits are available.
|
||||
bool AdvanceDigits() {
|
||||
if (decimal_start_ >= decimal_end_) return false;
|
||||
|
||||
uint32_t w = data_[decimal_start_++];
|
||||
for (size_ = 0; size_ < kDigitsPerChunk; w /= 10) {
|
||||
digits_[kDigitsPerChunk - ++size_] = w % 10 + '0';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
BinaryToDecimal(absl::Span<uint32_t> data, uint128 v, int exp) : data_(data) {
|
||||
// We need to print the digits directly into the sink object without
|
||||
// buffering them all first. To do this we need two things:
|
||||
// - to know the total number of digits to do padding when necessary
|
||||
// - to generate the decimal digits from the left.
|
||||
//
|
||||
// In order to do this, we do a two pass conversion.
|
||||
// On the first pass we convert the binary representation of the value into
|
||||
// a decimal representation in which each uint32_t chunk holds up to 9
|
||||
// decimal digits. In the second pass we take each decimal-holding-uint32_t
|
||||
// value and generate the ascii decimal digits into `digits_`.
|
||||
//
|
||||
// The binary and decimal representations actually share the same memory
|
||||
// region. As we go converting the chunks from binary to decimal we free
|
||||
// them up and reuse them for the decimal representation. One caveat is that
|
||||
// the decimal representation is around 7% less efficient in space than the
|
||||
// binary one. We allocate an extra 10% memory to account for this. See
|
||||
// ChunksNeeded for this calculation.
|
||||
size_t after_chunk_index = static_cast<size_t>(exp / 32 + 1);
|
||||
decimal_start_ = decimal_end_ = ChunksNeeded(exp);
|
||||
const int offset = exp % 32;
|
||||
// Left shift v by exp bits.
|
||||
data_[after_chunk_index - 1] = static_cast<uint32_t>(v << offset);
|
||||
for (v >>= (32 - offset); v; v >>= 32)
|
||||
data_[++after_chunk_index - 1] = static_cast<uint32_t>(v);
|
||||
|
||||
while (after_chunk_index > 0) {
|
||||
// While we have more than one chunk available, go in steps of 1e9.
|
||||
// `data_[after_chunk_index - 1]` holds the highest non-zero binary chunk,
|
||||
// so keep the variable updated.
|
||||
uint32_t carry = 0;
|
||||
for (size_t i = after_chunk_index; i > 0; --i) {
|
||||
uint64_t tmp = uint64_t{data_[i - 1]} + (uint64_t{carry} << 32);
|
||||
data_[i - 1] = static_cast<uint32_t>(tmp / uint64_t{1000000000});
|
||||
carry = static_cast<uint32_t>(tmp % uint64_t{1000000000});
|
||||
}
|
||||
|
||||
// If the highest chunk is now empty, remove it from view.
|
||||
if (data_[after_chunk_index - 1] == 0)
|
||||
--after_chunk_index;
|
||||
|
||||
--decimal_start_;
|
||||
assert(decimal_start_ != after_chunk_index - 1);
|
||||
data_[decimal_start_] = carry;
|
||||
}
|
||||
|
||||
// Fill the first set of digits. The first chunk might not be complete, so
|
||||
// handle differently.
|
||||
for (uint32_t first = data_[decimal_start_++]; first != 0; first /= 10) {
|
||||
digits_[kDigitsPerChunk - ++size_] = first % 10 + '0';
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr size_t kDigitsPerChunk = 9;
|
||||
|
||||
size_t decimal_start_;
|
||||
size_t decimal_end_;
|
||||
|
||||
char digits_[kDigitsPerChunk];
|
||||
size_t size_ = 0;
|
||||
|
||||
absl::Span<uint32_t> data_;
|
||||
};
|
||||
|
||||
// Converts a value of the form `x * 2^-exp` into a sequence of decimal digits.
|
||||
// Requires `-exp < 0` and
|
||||
// `-exp >= limits<MaxFloatType>::min_exponent - limits<MaxFloatType>::digits`.
|
||||
class FractionalDigitGenerator {
|
||||
public:
|
||||
// Run the conversion for `v * 2^exp` and call `f(generator)`.
|
||||
// This function will allocate enough stack space to perform the conversion.
|
||||
static void RunConversion(
|
||||
uint128 v, int exp, absl::FunctionRef<void(FractionalDigitGenerator)> f) {
|
||||
using Limits = std::numeric_limits<MaxFloatType>;
|
||||
assert(-exp < 0);
|
||||
assert(-exp >= Limits::min_exponent - 128);
|
||||
static_assert(StackArray::kMaxCapacity >=
|
||||
(Limits::digits + 128 - Limits::min_exponent + 31) / 32,
|
||||
"");
|
||||
StackArray::RunWithCapacity(
|
||||
static_cast<size_t>((Limits::digits + exp + 31) / 32),
|
||||
[=](absl::Span<uint32_t> input) {
|
||||
f(FractionalDigitGenerator(input, v, exp));
|
||||
});
|
||||
}
|
||||
|
||||
// Returns true if there are any more non-zero digits left.
|
||||
bool HasMoreDigits() const { return next_digit_ != 0 || after_chunk_index_; }
|
||||
|
||||
// Returns true if the remainder digits are greater than 5000...
|
||||
bool IsGreaterThanHalf() const {
|
||||
return next_digit_ > 5 || (next_digit_ == 5 && after_chunk_index_);
|
||||
}
|
||||
// Returns true if the remainder digits are exactly 5000...
|
||||
bool IsExactlyHalf() const { return next_digit_ == 5 && !after_chunk_index_; }
|
||||
|
||||
struct Digits {
|
||||
char digit_before_nine;
|
||||
size_t num_nines;
|
||||
};
|
||||
|
||||
// Get the next set of digits.
|
||||
// They are composed by a non-9 digit followed by a runs of zero or more 9s.
|
||||
Digits GetDigits() {
|
||||
Digits digits{next_digit_, 0};
|
||||
|
||||
next_digit_ = GetOneDigit();
|
||||
while (next_digit_ == 9) {
|
||||
++digits.num_nines;
|
||||
next_digit_ = GetOneDigit();
|
||||
}
|
||||
|
||||
return digits;
|
||||
}
|
||||
|
||||
private:
|
||||
// Return the next digit.
|
||||
char GetOneDigit() {
|
||||
if (!after_chunk_index_)
|
||||
return 0;
|
||||
|
||||
char carry = 0;
|
||||
for (size_t i = after_chunk_index_; i > 0; --i) {
|
||||
carry = MultiplyBy10WithCarry(&data_[i - 1], carry);
|
||||
}
|
||||
// If the lowest chunk is now empty, remove it from view.
|
||||
if (data_[after_chunk_index_ - 1] == 0)
|
||||
--after_chunk_index_;
|
||||
return carry;
|
||||
}
|
||||
|
||||
FractionalDigitGenerator(absl::Span<uint32_t> data, uint128 v, int exp)
|
||||
: after_chunk_index_(static_cast<size_t>(exp / 32 + 1)), data_(data) {
|
||||
const int offset = exp % 32;
|
||||
// Right shift `v` by `exp` bits.
|
||||
data_[after_chunk_index_ - 1] = static_cast<uint32_t>(v << (32 - offset));
|
||||
v >>= offset;
|
||||
// Make sure we don't overflow the data. We already calculated that
|
||||
// non-zero bits fit, so we might not have space for leading zero bits.
|
||||
for (size_t pos = after_chunk_index_ - 1; v; v >>= 32)
|
||||
data_[--pos] = static_cast<uint32_t>(v);
|
||||
|
||||
// Fill next_digit_, as GetDigits expects it to be populated always.
|
||||
next_digit_ = GetOneDigit();
|
||||
}
|
||||
|
||||
char next_digit_;
|
||||
size_t after_chunk_index_;
|
||||
absl::Span<uint32_t> data_;
|
||||
};
|
||||
|
||||
// Count the number of leading zero bits.
|
||||
int LeadingZeros(uint64_t v) { return countl_zero(v); }
|
||||
int LeadingZeros(uint128 v) {
|
||||
auto high = static_cast<uint64_t>(v >> 64);
|
||||
auto low = static_cast<uint64_t>(v);
|
||||
return high != 0 ? countl_zero(high) : 64 + countl_zero(low);
|
||||
}
|
||||
|
||||
// Round up the text digits starting at `p`.
|
||||
// The buffer must have an extra digit that is known to not need rounding.
|
||||
// This is done below by having an extra '0' digit on the left.
|
||||
void RoundUp(char *p) {
|
||||
while (*p == '9' || *p == '.') {
|
||||
if (*p == '9') *p = '0';
|
||||
--p;
|
||||
}
|
||||
++*p;
|
||||
}
|
||||
|
||||
// Check the previous digit and round up or down to follow the round-to-even
|
||||
// policy.
|
||||
void RoundToEven(char *p) {
|
||||
if (*p == '.') --p;
|
||||
if (*p % 2 == 1) RoundUp(p);
|
||||
}
|
||||
|
||||
// Simple integral decimal digit printing for values that fit in 64-bits.
|
||||
// Returns the pointer to the last written digit.
|
||||
char *PrintIntegralDigitsFromRightFast(uint64_t v, char *p) {
|
||||
do {
|
||||
*--p = DivideBy10WithCarry(&v, 0) + '0';
|
||||
} while (v != 0);
|
||||
return p;
|
||||
}
|
||||
|
||||
// Simple integral decimal digit printing for values that fit in 128-bits.
|
||||
// Returns the pointer to the last written digit.
|
||||
char *PrintIntegralDigitsFromRightFast(uint128 v, char *p) {
|
||||
auto high = static_cast<uint64_t>(v >> 64);
|
||||
auto low = static_cast<uint64_t>(v);
|
||||
|
||||
while (high != 0) {
|
||||
char carry = DivideBy10WithCarry(&high, 0);
|
||||
carry = DivideBy10WithCarry(&low, carry);
|
||||
*--p = carry + '0';
|
||||
}
|
||||
return PrintIntegralDigitsFromRightFast(low, p);
|
||||
}
|
||||
|
||||
// Simple fractional decimal digit printing for values that fir in 64-bits after
|
||||
// shifting.
|
||||
// Performs rounding if necessary to fit within `precision`.
|
||||
// Returns the pointer to one after the last character written.
|
||||
char* PrintFractionalDigitsFast(uint64_t v,
|
||||
char* start,
|
||||
int exp,
|
||||
size_t precision) {
|
||||
char *p = start;
|
||||
v <<= (64 - exp);
|
||||
while (precision > 0) {
|
||||
if (!v) return p;
|
||||
*p++ = MultiplyBy10WithCarry(&v, 0) + '0';
|
||||
--precision;
|
||||
}
|
||||
|
||||
// We need to round.
|
||||
if (v < 0x8000000000000000) {
|
||||
// We round down, so nothing to do.
|
||||
} else if (v > 0x8000000000000000) {
|
||||
// We round up.
|
||||
RoundUp(p - 1);
|
||||
} else {
|
||||
RoundToEven(p - 1);
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
// Simple fractional decimal digit printing for values that fir in 128-bits
|
||||
// after shifting.
|
||||
// Performs rounding if necessary to fit within `precision`.
|
||||
// Returns the pointer to one after the last character written.
|
||||
char* PrintFractionalDigitsFast(uint128 v,
|
||||
char* start,
|
||||
int exp,
|
||||
size_t precision) {
|
||||
char *p = start;
|
||||
v <<= (128 - exp);
|
||||
auto high = static_cast<uint64_t>(v >> 64);
|
||||
auto low = static_cast<uint64_t>(v);
|
||||
|
||||
// While we have digits to print and `low` is not empty, do the long
|
||||
// multiplication.
|
||||
while (precision > 0 && low != 0) {
|
||||
char carry = MultiplyBy10WithCarry(&low, 0);
|
||||
carry = MultiplyBy10WithCarry(&high, carry);
|
||||
|
||||
*p++ = carry + '0';
|
||||
--precision;
|
||||
}
|
||||
|
||||
// Now `low` is empty, so use a faster approach for the rest of the digits.
|
||||
// This block is pretty much the same as the main loop for the 64-bit case
|
||||
// above.
|
||||
while (precision > 0) {
|
||||
if (!high) return p;
|
||||
*p++ = MultiplyBy10WithCarry(&high, 0) + '0';
|
||||
--precision;
|
||||
}
|
||||
|
||||
// We need to round.
|
||||
if (high < 0x8000000000000000) {
|
||||
// We round down, so nothing to do.
|
||||
} else if (high > 0x8000000000000000 || low != 0) {
|
||||
// We round up.
|
||||
RoundUp(p - 1);
|
||||
} else {
|
||||
RoundToEven(p - 1);
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
struct FormatState {
|
||||
char sign_char;
|
||||
size_t precision;
|
||||
const FormatConversionSpecImpl &conv;
|
||||
FormatSinkImpl *sink;
|
||||
|
||||
// In `alt` mode (flag #) we keep the `.` even if there are no fractional
|
||||
// digits. In non-alt mode, we strip it.
|
||||
bool ShouldPrintDot() const { return precision != 0 || conv.has_alt_flag(); }
|
||||
};
|
||||
|
||||
struct Padding {
|
||||
size_t left_spaces;
|
||||
size_t zeros;
|
||||
size_t right_spaces;
|
||||
};
|
||||
|
||||
Padding ExtraWidthToPadding(size_t total_size, const FormatState &state) {
|
||||
if (state.conv.width() < 0 ||
|
||||
static_cast<size_t>(state.conv.width()) <= total_size) {
|
||||
return {0, 0, 0};
|
||||
}
|
||||
size_t missing_chars = static_cast<size_t>(state.conv.width()) - total_size;
|
||||
if (state.conv.has_left_flag()) {
|
||||
return {0, 0, missing_chars};
|
||||
} else if (state.conv.has_zero_flag()) {
|
||||
return {0, missing_chars, 0};
|
||||
} else {
|
||||
return {missing_chars, 0, 0};
|
||||
}
|
||||
}
|
||||
|
||||
void FinalPrint(const FormatState& state,
|
||||
absl::string_view data,
|
||||
size_t padding_offset,
|
||||
size_t 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() +
|
||||
data_postfix.size() + 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.substr(padding_offset));
|
||||
state.sink->Append(trailing_zeros, '0');
|
||||
state.sink->Append(data_postfix);
|
||||
state.sink->Append(padding.right_spaces, ' ');
|
||||
}
|
||||
|
||||
// Fastpath %f formatter for when the shifted value fits in a simple integral
|
||||
// type.
|
||||
// Prints `v*2^exp` with the options from `state`.
|
||||
template <typename Int>
|
||||
void FormatFFast(Int v, int exp, const FormatState &state) {
|
||||
constexpr int input_bits = sizeof(Int) * 8;
|
||||
|
||||
static constexpr size_t integral_size =
|
||||
/* in case we need to round up an extra digit */ 1 +
|
||||
/* decimal digits for uint128 */ 40 + 1;
|
||||
char buffer[integral_size + /* . */ 1 + /* max digits uint128 */ 128];
|
||||
buffer[integral_size] = '.';
|
||||
char *const integral_digits_end = buffer + integral_size;
|
||||
char *integral_digits_start;
|
||||
char *const fractional_digits_start = buffer + integral_size + 1;
|
||||
char *fractional_digits_end = fractional_digits_start;
|
||||
|
||||
if (exp >= 0) {
|
||||
const int total_bits = input_bits - LeadingZeros(v) + exp;
|
||||
integral_digits_start =
|
||||
total_bits <= 64
|
||||
? PrintIntegralDigitsFromRightFast(static_cast<uint64_t>(v) << exp,
|
||||
integral_digits_end)
|
||||
: PrintIntegralDigitsFromRightFast(static_cast<uint128>(v) << exp,
|
||||
integral_digits_end);
|
||||
} else {
|
||||
exp = -exp;
|
||||
|
||||
integral_digits_start = PrintIntegralDigitsFromRightFast(
|
||||
exp < input_bits ? v >> exp : 0, integral_digits_end);
|
||||
// PrintFractionalDigits may pull a carried 1 all the way up through the
|
||||
// integral portion.
|
||||
integral_digits_start[-1] = '0';
|
||||
|
||||
fractional_digits_end =
|
||||
exp <= 64 ? PrintFractionalDigitsFast(v, fractional_digits_start, exp,
|
||||
state.precision)
|
||||
: PrintFractionalDigitsFast(static_cast<uint128>(v),
|
||||
fractional_digits_start, exp,
|
||||
state.precision);
|
||||
// There was a carry, so include the first digit too.
|
||||
if (integral_digits_start[-1] != '0') --integral_digits_start;
|
||||
}
|
||||
|
||||
size_t size =
|
||||
static_cast<size_t>(fractional_digits_end - integral_digits_start);
|
||||
|
||||
// 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(state, absl::string_view(integral_digits_start, size),
|
||||
/*padding_offset=*/0,
|
||||
state.precision - static_cast<size_t>(fractional_digits_end -
|
||||
fractional_digits_start),
|
||||
/*data_postfix=*/"");
|
||||
}
|
||||
|
||||
// Slow %f formatter for when the shifted value does not fit in a uint128, and
|
||||
// `exp > 0`.
|
||||
// Prints `v*2^exp` with the options from `state`.
|
||||
// This one is guaranteed to not have fractional digits, so we don't have to
|
||||
// worry about anything after the `.`.
|
||||
void FormatFPositiveExpSlow(uint128 v, int exp, const FormatState &state) {
|
||||
BinaryToDecimal::RunConversion(v, exp, [&](BinaryToDecimal btd) {
|
||||
const size_t total_digits =
|
||||
btd.TotalDigits() + (state.ShouldPrintDot() ? state.precision + 1 : 0);
|
||||
|
||||
const auto padding = ExtraWidthToPadding(
|
||||
total_digits + (state.sign_char != '\0' ? 1 : 0), state);
|
||||
|
||||
state.sink->Append(padding.left_spaces, ' ');
|
||||
if (state.sign_char != '\0')
|
||||
state.sink->Append(1, state.sign_char);
|
||||
state.sink->Append(padding.zeros, '0');
|
||||
|
||||
do {
|
||||
state.sink->Append(btd.CurrentDigits());
|
||||
} while (btd.AdvanceDigits());
|
||||
|
||||
if (state.ShouldPrintDot())
|
||||
state.sink->Append(1, '.');
|
||||
state.sink->Append(state.precision, '0');
|
||||
state.sink->Append(padding.right_spaces, ' ');
|
||||
});
|
||||
}
|
||||
|
||||
// Slow %f formatter for when the shifted value does not fit in a uint128, and
|
||||
// `exp < 0`.
|
||||
// Prints `v*2^exp` with the options from `state`.
|
||||
// This one is guaranteed to be < 1.0, so we don't have to worry about integral
|
||||
// digits.
|
||||
void FormatFNegativeExpSlow(uint128 v, int exp, const FormatState &state) {
|
||||
const size_t total_digits =
|
||||
/* 0 */ 1 + (state.ShouldPrintDot() ? state.precision + 1 : 0);
|
||||
auto padding =
|
||||
ExtraWidthToPadding(total_digits + (state.sign_char ? 1 : 0), state);
|
||||
padding.zeros += 1;
|
||||
state.sink->Append(padding.left_spaces, ' ');
|
||||
if (state.sign_char != '\0') state.sink->Append(1, state.sign_char);
|
||||
state.sink->Append(padding.zeros, '0');
|
||||
|
||||
if (state.ShouldPrintDot()) state.sink->Append(1, '.');
|
||||
|
||||
// Print digits
|
||||
size_t digits_to_go = state.precision;
|
||||
|
||||
FractionalDigitGenerator::RunConversion(
|
||||
v, exp, [&](FractionalDigitGenerator digit_gen) {
|
||||
// There are no digits to print here.
|
||||
if (state.precision == 0) return;
|
||||
|
||||
// We go one digit at a time, while keeping track of runs of nines.
|
||||
// The runs of nines are used to perform rounding when necessary.
|
||||
|
||||
while (digits_to_go > 0 && digit_gen.HasMoreDigits()) {
|
||||
auto digits = digit_gen.GetDigits();
|
||||
|
||||
// Now we have a digit and a run of nines.
|
||||
// See if we can print them all.
|
||||
if (digits.num_nines + 1 < digits_to_go) {
|
||||
// We don't have to round yet, so print them.
|
||||
state.sink->Append(1, digits.digit_before_nine + '0');
|
||||
state.sink->Append(digits.num_nines, '9');
|
||||
digits_to_go -= digits.num_nines + 1;
|
||||
|
||||
} else {
|
||||
// We can't print all the nines, see where we have to truncate.
|
||||
|
||||
bool round_up = false;
|
||||
if (digits.num_nines + 1 > digits_to_go) {
|
||||
// We round up at a nine. No need to print them.
|
||||
round_up = true;
|
||||
} else {
|
||||
// We can fit all the nines, but truncate just after it.
|
||||
if (digit_gen.IsGreaterThanHalf()) {
|
||||
round_up = true;
|
||||
} else if (digit_gen.IsExactlyHalf()) {
|
||||
// Round to even
|
||||
round_up =
|
||||
digits.num_nines != 0 || digits.digit_before_nine % 2 == 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (round_up) {
|
||||
state.sink->Append(1, digits.digit_before_nine + '1');
|
||||
--digits_to_go;
|
||||
// The rest will be zeros.
|
||||
} else {
|
||||
state.sink->Append(1, digits.digit_before_nine + '0');
|
||||
state.sink->Append(digits_to_go - 1, '9');
|
||||
digits_to_go = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
state.sink->Append(digits_to_go, '0');
|
||||
state.sink->Append(padding.right_spaces, ' ');
|
||||
}
|
||||
|
||||
template <typename Int>
|
||||
void FormatF(Int mantissa, int exp, const FormatState &state) {
|
||||
if (exp >= 0) {
|
||||
const int total_bits =
|
||||
static_cast<int>(sizeof(Int) * 8) - LeadingZeros(mantissa) + exp;
|
||||
|
||||
// Fallback to the slow stack-based approach if we can't do it in a 64 or
|
||||
// 128 bit state.
|
||||
if (ABSL_PREDICT_FALSE(total_bits > 128)) {
|
||||
return FormatFPositiveExpSlow(mantissa, exp, state);
|
||||
}
|
||||
} else {
|
||||
// Fallback to the slow stack-based approach if we can't do it in a 64 or
|
||||
// 128 bit state.
|
||||
if (ABSL_PREDICT_FALSE(exp < -128)) {
|
||||
return FormatFNegativeExpSlow(mantissa, -exp, 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, size_t nibble_index) {
|
||||
constexpr Int mask_low_nibble = Int{0xf};
|
||||
int shift = static_cast<int>(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(size_t nibble_index, Int* n) {
|
||||
constexpr size_t kShift = sizeof(Int) * 8 - 1;
|
||||
constexpr size_t kNumNibbles = sizeof(Int) * 8 / 4;
|
||||
Int before = *n >> kShift;
|
||||
// Here we essentially want to take the number 1 and move it into the
|
||||
// requested 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} << static_cast<int>(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(size_t nibble_index) {
|
||||
constexpr size_t kNumNibbles = sizeof(Int) * 8 / 4;
|
||||
static const Int ones = ~Int{0};
|
||||
++nibble_index;
|
||||
return ones >> static_cast<int>(
|
||||
4 * (std::max(kNumNibbles, nibble_index) - nibble_index));
|
||||
}
|
||||
|
||||
// Return a mask with 1's below the given nibble.
|
||||
template <typename Int>
|
||||
Int MaskUpToNibbleExclusive(size_t nibble_index) {
|
||||
return nibble_index == 0 ? 0 : MaskUpToNibbleInclusive<Int>(nibble_index - 1);
|
||||
}
|
||||
|
||||
template <typename Int>
|
||||
Int MoveToNibble(uint8_t nibble, size_t nibble_index) {
|
||||
return Int{nibble} << static_cast<int>(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 size_t HexFloatLeadingDigitSizeInBits() {
|
||||
return std::numeric_limits<Float>::digits % 4 > 0
|
||||
? static_cast<size_t>(std::numeric_limits<Float>::digits % 4)
|
||||
: size_t{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,
|
||||
size_t 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;
|
||||
}
|
||||
size_t rounding_nibble_idx = final_nibble_displayed - 1;
|
||||
constexpr size_t 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;
|
||||
size_t 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 size_t kTotalNibbles = sizeof(Int) * 8 / 4;
|
||||
// Index of the last nibble that we could display given precision.
|
||||
size_t final_nibble_displayed =
|
||||
precision_specified
|
||||
? (std::max(kTotalNibbles, state.precision) - 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 size_t kIntBits = sizeof(Int) * 8;
|
||||
static const Int kHighIntBit = Int{1} << (kIntBits - 1);
|
||||
const size_t 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 >> static_cast<int>(kIntBits - kLeadDigitBitsCount));
|
||||
*exp -= (*mantissa != 0) ? static_cast<int>(kLeadDigitBitsCount) : *exp;
|
||||
*mantissa <<= static_cast<int>(kLeadDigitBitsCount);
|
||||
}
|
||||
|
||||
template <typename Int>
|
||||
void FormatA(const HexFloatTypeParams float_traits, Int mantissa, int exp,
|
||||
bool uppercase, const FormatState &state) {
|
||||
// Int properties.
|
||||
constexpr size_t kIntBits = sizeof(Int) * 8;
|
||||
constexpr size_t 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<MaxFloatType>::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 ============
|
||||
size_t digits_emitted = 0;
|
||||
while (mantissa > 0) {
|
||||
*digits_iter++ = digits[GetNibble(mantissa, kTotalNibbles - 1)];
|
||||
mantissa <<= 4;
|
||||
++digits_emitted;
|
||||
}
|
||||
size_t trailing_zeros = 0;
|
||||
if (precision_specified) {
|
||||
assert(state.precision >= digits_emitted);
|
||||
trailing_zeros = state.precision - digits_emitted;
|
||||
}
|
||||
auto digits_result = string_view(
|
||||
digits_buffer, static_cast<size_t>(digits_iter - digits_buffer));
|
||||
|
||||
// =============== Exponent ==================
|
||||
constexpr size_t kBufSizeForExpDecRepr =
|
||||
numbers_internal::kFastToBufferSize // required 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 of any padding
|
||||
static_cast<size_t>(trailing_zeros), // remaining mantissa padding
|
||||
exp_buffer); // exponent
|
||||
}
|
||||
|
||||
char *CopyStringTo(absl::string_view v, char *out) {
|
||||
std::memcpy(out, v.data(), v.size());
|
||||
return out + v.size();
|
||||
}
|
||||
|
||||
template <typename Float>
|
||||
bool FallbackToSnprintf(const Float v, const FormatConversionSpecImpl &conv,
|
||||
FormatSinkImpl *sink) {
|
||||
int w = conv.width() >= 0 ? conv.width() : 0;
|
||||
int p = conv.precision() >= 0 ? conv.precision() : -1;
|
||||
char fmt[32];
|
||||
{
|
||||
char *fp = fmt;
|
||||
*fp++ = '%';
|
||||
fp = CopyStringTo(FormatConversionSpecImplFriend::FlagsToString(conv), fp);
|
||||
fp = CopyStringTo("*.*", fp);
|
||||
if (std::is_same<long double, Float>()) {
|
||||
*fp++ = 'L';
|
||||
}
|
||||
*fp++ = FormatConversionCharToChar(conv.conversion_char());
|
||||
*fp = 0;
|
||||
assert(fp < fmt + sizeof(fmt));
|
||||
}
|
||||
std::string space(512, '\0');
|
||||
absl::string_view result;
|
||||
while (true) {
|
||||
int n = snprintf(&space[0], space.size(), fmt, w, p, v);
|
||||
if (n < 0) return false;
|
||||
if (static_cast<size_t>(n) < space.size()) {
|
||||
result = absl::string_view(space.data(), static_cast<size_t>(n));
|
||||
break;
|
||||
}
|
||||
space.resize(static_cast<size_t>(n) + 1);
|
||||
}
|
||||
sink->Append(result);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 128-bits in decimal: ceil(128*log(2)/log(10))
|
||||
// or std::numeric_limits<__uint128_t>::digits10
|
||||
constexpr size_t kMaxFixedPrecision = 39;
|
||||
|
||||
constexpr size_t kBufferLength = /*sign*/ 1 +
|
||||
/*integer*/ kMaxFixedPrecision +
|
||||
/*point*/ 1 +
|
||||
/*fraction*/ kMaxFixedPrecision +
|
||||
/*exponent e+123*/ 5;
|
||||
|
||||
struct Buffer {
|
||||
void push_front(char c) {
|
||||
assert(begin > data);
|
||||
*--begin = c;
|
||||
}
|
||||
void push_back(char c) {
|
||||
assert(end < data + sizeof(data));
|
||||
*end++ = c;
|
||||
}
|
||||
void pop_back() {
|
||||
assert(begin < end);
|
||||
--end;
|
||||
}
|
||||
|
||||
char &back() const {
|
||||
assert(begin < end);
|
||||
return end[-1];
|
||||
}
|
||||
|
||||
char last_digit() const { return end[-1] == '.' ? end[-2] : end[-1]; }
|
||||
|
||||
size_t size() const { return static_cast<size_t>(end - begin); }
|
||||
|
||||
char data[kBufferLength];
|
||||
char *begin;
|
||||
char *end;
|
||||
};
|
||||
|
||||
enum class FormatStyle { Fixed, Precision };
|
||||
|
||||
// If the value is Inf or Nan, print it and return true.
|
||||
// Otherwise, return false.
|
||||
template <typename Float>
|
||||
bool ConvertNonNumericFloats(char sign_char, Float v,
|
||||
const FormatConversionSpecImpl &conv,
|
||||
FormatSinkImpl *sink) {
|
||||
char text[4], *ptr = text;
|
||||
if (sign_char != '\0') *ptr++ = sign_char;
|
||||
if (std::isnan(v)) {
|
||||
ptr = std::copy_n(
|
||||
FormatConversionCharIsUpper(conv.conversion_char()) ? "NAN" : "nan", 3,
|
||||
ptr);
|
||||
} else if (std::isinf(v)) {
|
||||
ptr = std::copy_n(
|
||||
FormatConversionCharIsUpper(conv.conversion_char()) ? "INF" : "inf", 3,
|
||||
ptr);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return sink->PutPaddedString(
|
||||
string_view(text, static_cast<size_t>(ptr - text)), conv.width(), -1,
|
||||
conv.has_left_flag());
|
||||
}
|
||||
|
||||
// Round up the last digit of the value.
|
||||
// It will carry over and potentially overflow. 'exp' will be adjusted in that
|
||||
// case.
|
||||
template <FormatStyle mode>
|
||||
void RoundUp(Buffer *buffer, int *exp) {
|
||||
char *p = &buffer->back();
|
||||
while (p >= buffer->begin && (*p == '9' || *p == '.')) {
|
||||
if (*p == '9') *p = '0';
|
||||
--p;
|
||||
}
|
||||
|
||||
if (p < buffer->begin) {
|
||||
*p = '1';
|
||||
buffer->begin = p;
|
||||
if (mode == FormatStyle::Precision) {
|
||||
std::swap(p[1], p[2]); // move the .
|
||||
++*exp;
|
||||
buffer->pop_back();
|
||||
}
|
||||
} else {
|
||||
++*p;
|
||||
}
|
||||
}
|
||||
|
||||
void PrintExponent(int exp, char e, Buffer *out) {
|
||||
out->push_back(e);
|
||||
if (exp < 0) {
|
||||
out->push_back('-');
|
||||
exp = -exp;
|
||||
} else {
|
||||
out->push_back('+');
|
||||
}
|
||||
// Exponent digits.
|
||||
if (exp > 99) {
|
||||
out->push_back(static_cast<char>(exp / 100 + '0'));
|
||||
out->push_back(static_cast<char>(exp / 10 % 10 + '0'));
|
||||
out->push_back(static_cast<char>(exp % 10 + '0'));
|
||||
} else {
|
||||
out->push_back(static_cast<char>(exp / 10 + '0'));
|
||||
out->push_back(static_cast<char>(exp % 10 + '0'));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Float, typename Int>
|
||||
constexpr bool CanFitMantissa() {
|
||||
return
|
||||
#if defined(__clang__) && (__clang_major__ < 9) && !defined(__SSE3__)
|
||||
// Workaround for clang bug: https://bugs.llvm.org/show_bug.cgi?id=38289
|
||||
// Casting from long double to uint64_t is miscompiled and drops bits.
|
||||
(!std::is_same<Float, long double>::value ||
|
||||
!std::is_same<Int, uint64_t>::value) &&
|
||||
#endif
|
||||
std::numeric_limits<Float>::digits <= std::numeric_limits<Int>::digits;
|
||||
}
|
||||
|
||||
template <typename Float>
|
||||
struct Decomposed {
|
||||
using MantissaType =
|
||||
absl::conditional_t<std::is_same<long double, Float>::value, uint128,
|
||||
uint64_t>;
|
||||
static_assert(std::numeric_limits<Float>::digits <= sizeof(MantissaType) * 8,
|
||||
"");
|
||||
MantissaType mantissa;
|
||||
int exponent;
|
||||
};
|
||||
|
||||
// Decompose the double into an integer mantissa and an exponent.
|
||||
template <typename Float>
|
||||
Decomposed<Float> Decompose(Float v) {
|
||||
int exp;
|
||||
Float m = std::frexp(v, &exp);
|
||||
m = std::ldexp(m, std::numeric_limits<Float>::digits);
|
||||
exp -= std::numeric_limits<Float>::digits;
|
||||
|
||||
return {static_cast<typename Decomposed<Float>::MantissaType>(m), exp};
|
||||
}
|
||||
|
||||
// Print 'digits' as decimal.
|
||||
// In Fixed mode, we add a '.' at the end.
|
||||
// In Precision mode, we add a '.' after the first digit.
|
||||
template <FormatStyle mode, typename Int>
|
||||
size_t PrintIntegralDigits(Int digits, Buffer* out) {
|
||||
size_t printed = 0;
|
||||
if (digits) {
|
||||
for (; digits; digits /= 10) out->push_front(digits % 10 + '0');
|
||||
printed = out->size();
|
||||
if (mode == FormatStyle::Precision) {
|
||||
out->push_front(*out->begin);
|
||||
out->begin[1] = '.';
|
||||
} else {
|
||||
out->push_back('.');
|
||||
}
|
||||
} else if (mode == FormatStyle::Fixed) {
|
||||
out->push_front('0');
|
||||
out->push_back('.');
|
||||
printed = 1;
|
||||
}
|
||||
return printed;
|
||||
}
|
||||
|
||||
// Back out 'extra_digits' digits and round up if necessary.
|
||||
void RemoveExtraPrecision(size_t extra_digits,
|
||||
bool has_leftover_value,
|
||||
Buffer* out,
|
||||
int* exp_out) {
|
||||
// Back out the extra digits
|
||||
out->end -= extra_digits;
|
||||
|
||||
bool needs_to_round_up = [&] {
|
||||
// We look at the digit just past the end.
|
||||
// There must be 'extra_digits' extra valid digits after end.
|
||||
if (*out->end > '5') return true;
|
||||
if (*out->end < '5') return false;
|
||||
if (has_leftover_value || std::any_of(out->end + 1, out->end + extra_digits,
|
||||
[](char c) { return c != '0'; }))
|
||||
return true;
|
||||
|
||||
// Ends in ...50*, round to even.
|
||||
return out->last_digit() % 2 == 1;
|
||||
}();
|
||||
|
||||
if (needs_to_round_up) {
|
||||
RoundUp<FormatStyle::Precision>(out, exp_out);
|
||||
}
|
||||
}
|
||||
|
||||
// Print the value into the buffer.
|
||||
// This will not include the exponent, which will be returned in 'exp_out' for
|
||||
// Precision mode.
|
||||
template <typename Int, typename Float, FormatStyle mode>
|
||||
bool FloatToBufferImpl(Int int_mantissa,
|
||||
int exp,
|
||||
size_t precision,
|
||||
Buffer* out,
|
||||
int* exp_out) {
|
||||
assert((CanFitMantissa<Float, Int>()));
|
||||
|
||||
const int int_bits = std::numeric_limits<Int>::digits;
|
||||
|
||||
// In precision mode, we start printing one char to the right because it will
|
||||
// also include the '.'
|
||||
// In fixed mode we put the dot afterwards on the right.
|
||||
out->begin = out->end =
|
||||
out->data + 1 + kMaxFixedPrecision + (mode == FormatStyle::Precision);
|
||||
|
||||
if (exp >= 0) {
|
||||
if (std::numeric_limits<Float>::digits + exp > int_bits) {
|
||||
// The value will overflow the Int
|
||||
return false;
|
||||
}
|
||||
size_t digits_printed = PrintIntegralDigits<mode>(int_mantissa << exp, out);
|
||||
size_t digits_to_zero_pad = precision;
|
||||
if (mode == FormatStyle::Precision) {
|
||||
*exp_out = static_cast<int>(digits_printed - 1);
|
||||
if (digits_to_zero_pad < digits_printed - 1) {
|
||||
RemoveExtraPrecision(digits_printed - 1 - digits_to_zero_pad, false,
|
||||
out, exp_out);
|
||||
return true;
|
||||
}
|
||||
digits_to_zero_pad -= digits_printed - 1;
|
||||
}
|
||||
for (; digits_to_zero_pad-- > 0;) out->push_back('0');
|
||||
return true;
|
||||
}
|
||||
|
||||
exp = -exp;
|
||||
// We need at least 4 empty bits for the next decimal digit.
|
||||
// We will multiply by 10.
|
||||
if (exp > int_bits - 4) return false;
|
||||
|
||||
const Int mask = (Int{1} << exp) - 1;
|
||||
|
||||
// Print the integral part first.
|
||||
size_t digits_printed = PrintIntegralDigits<mode>(int_mantissa >> exp, out);
|
||||
int_mantissa &= mask;
|
||||
|
||||
size_t fractional_count = precision;
|
||||
if (mode == FormatStyle::Precision) {
|
||||
if (digits_printed == 0) {
|
||||
// Find the first non-zero digit, when in Precision mode.
|
||||
*exp_out = 0;
|
||||
if (int_mantissa) {
|
||||
while (int_mantissa <= mask) {
|
||||
int_mantissa *= 10;
|
||||
--*exp_out;
|
||||
}
|
||||
}
|
||||
out->push_front(static_cast<char>(int_mantissa >> exp) + '0');
|
||||
out->push_back('.');
|
||||
int_mantissa &= mask;
|
||||
} else {
|
||||
// We already have a digit, and a '.'
|
||||
*exp_out = static_cast<int>(digits_printed - 1);
|
||||
if (fractional_count < digits_printed - 1) {
|
||||
// If we had enough digits, return right away.
|
||||
// The code below will try to round again otherwise.
|
||||
RemoveExtraPrecision(digits_printed - 1 - fractional_count,
|
||||
int_mantissa != 0, out, exp_out);
|
||||
return true;
|
||||
}
|
||||
fractional_count -= digits_printed - 1;
|
||||
}
|
||||
}
|
||||
|
||||
auto get_next_digit = [&] {
|
||||
int_mantissa *= 10;
|
||||
char digit = static_cast<char>(int_mantissa >> exp);
|
||||
int_mantissa &= mask;
|
||||
return digit;
|
||||
};
|
||||
|
||||
// Print fractional_count more digits, if available.
|
||||
for (; fractional_count > 0; --fractional_count) {
|
||||
out->push_back(get_next_digit() + '0');
|
||||
}
|
||||
|
||||
char next_digit = get_next_digit();
|
||||
if (next_digit > 5 ||
|
||||
(next_digit == 5 && (int_mantissa || out->last_digit() % 2 == 1))) {
|
||||
RoundUp<mode>(out, exp_out);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <FormatStyle mode, typename Float>
|
||||
bool FloatToBuffer(Decomposed<Float> decomposed,
|
||||
size_t precision,
|
||||
Buffer* out,
|
||||
int* exp) {
|
||||
if (precision > kMaxFixedPrecision) return false;
|
||||
|
||||
// Try with uint64_t.
|
||||
if (CanFitMantissa<Float, std::uint64_t>() &&
|
||||
FloatToBufferImpl<std::uint64_t, Float, mode>(
|
||||
static_cast<std::uint64_t>(decomposed.mantissa), decomposed.exponent,
|
||||
precision, out, exp))
|
||||
return true;
|
||||
|
||||
#if defined(ABSL_HAVE_INTRINSIC_INT128)
|
||||
// If that is not enough, try with __uint128_t.
|
||||
return CanFitMantissa<Float, __uint128_t>() &&
|
||||
FloatToBufferImpl<__uint128_t, Float, mode>(
|
||||
static_cast<__uint128_t>(decomposed.mantissa), decomposed.exponent,
|
||||
precision, out, exp);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
void WriteBufferToSink(char sign_char, absl::string_view str,
|
||||
const FormatConversionSpecImpl &conv,
|
||||
FormatSinkImpl *sink) {
|
||||
size_t left_spaces = 0, zeros = 0, right_spaces = 0;
|
||||
size_t missing_chars = 0;
|
||||
if (conv.width() >= 0) {
|
||||
const size_t conv_width_size_t = static_cast<size_t>(conv.width());
|
||||
const size_t existing_chars =
|
||||
str.size() + static_cast<size_t>(sign_char != 0);
|
||||
if (conv_width_size_t > existing_chars)
|
||||
missing_chars = conv_width_size_t - existing_chars;
|
||||
}
|
||||
if (conv.has_left_flag()) {
|
||||
right_spaces = missing_chars;
|
||||
} else if (conv.has_zero_flag()) {
|
||||
zeros = missing_chars;
|
||||
} else {
|
||||
left_spaces = missing_chars;
|
||||
}
|
||||
|
||||
sink->Append(left_spaces, ' ');
|
||||
if (sign_char != '\0') sink->Append(1, sign_char);
|
||||
sink->Append(zeros, '0');
|
||||
sink->Append(str);
|
||||
sink->Append(right_spaces, ' ');
|
||||
}
|
||||
|
||||
template <typename Float>
|
||||
bool FloatToSink(const Float v, const FormatConversionSpecImpl &conv,
|
||||
FormatSinkImpl *sink) {
|
||||
// Print the sign or the sign column.
|
||||
Float abs_v = v;
|
||||
char sign_char = 0;
|
||||
if (std::signbit(abs_v)) {
|
||||
sign_char = '-';
|
||||
abs_v = -abs_v;
|
||||
} else if (conv.has_show_pos_flag()) {
|
||||
sign_char = '+';
|
||||
} else if (conv.has_sign_col_flag()) {
|
||||
sign_char = ' ';
|
||||
}
|
||||
|
||||
// Print nan/inf.
|
||||
if (ConvertNonNumericFloats(sign_char, abs_v, conv, sink)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t precision =
|
||||
conv.precision() < 0 ? 6 : static_cast<size_t>(conv.precision());
|
||||
|
||||
int exp = 0;
|
||||
|
||||
auto decomposed = Decompose(abs_v);
|
||||
|
||||
Buffer buffer;
|
||||
|
||||
FormatConversionChar c = conv.conversion_char();
|
||||
|
||||
if (c == FormatConversionCharInternal::f ||
|
||||
c == FormatConversionCharInternal::F) {
|
||||
FormatF(decomposed.mantissa, decomposed.exponent,
|
||||
{sign_char, precision, conv, sink});
|
||||
return true;
|
||||
} else if (c == FormatConversionCharInternal::e ||
|
||||
c == FormatConversionCharInternal::E) {
|
||||
if (!FloatToBuffer<FormatStyle::Precision>(decomposed, precision, &buffer,
|
||||
&exp)) {
|
||||
return FallbackToSnprintf(v, conv, sink);
|
||||
}
|
||||
if (!conv.has_alt_flag() && buffer.back() == '.') buffer.pop_back();
|
||||
PrintExponent(
|
||||
exp, FormatConversionCharIsUpper(conv.conversion_char()) ? 'E' : 'e',
|
||||
&buffer);
|
||||
} else if (c == FormatConversionCharInternal::g ||
|
||||
c == FormatConversionCharInternal::G) {
|
||||
precision = std::max(precision, size_t{1}) - 1;
|
||||
if (!FloatToBuffer<FormatStyle::Precision>(decomposed, precision, &buffer,
|
||||
&exp)) {
|
||||
return FallbackToSnprintf(v, conv, sink);
|
||||
}
|
||||
if ((exp < 0 || precision + 1 > static_cast<size_t>(exp)) && exp >= -4) {
|
||||
if (exp < 0) {
|
||||
// Have 1.23456, needs 0.00123456
|
||||
// Move the first digit
|
||||
buffer.begin[1] = *buffer.begin;
|
||||
// Add some zeros
|
||||
for (; exp < -1; ++exp) *buffer.begin-- = '0';
|
||||
*buffer.begin-- = '.';
|
||||
*buffer.begin = '0';
|
||||
} else if (exp > 0) {
|
||||
// Have 1.23456, needs 1234.56
|
||||
// Move the '.' exp positions to the right.
|
||||
std::rotate(buffer.begin + 1, buffer.begin + 2, buffer.begin + exp + 2);
|
||||
}
|
||||
exp = 0;
|
||||
}
|
||||
if (!conv.has_alt_flag()) {
|
||||
while (buffer.back() == '0') buffer.pop_back();
|
||||
if (buffer.back() == '.') buffer.pop_back();
|
||||
}
|
||||
if (exp) {
|
||||
PrintExponent(
|
||||
exp, FormatConversionCharIsUpper(conv.conversion_char()) ? 'E' : 'e',
|
||||
&buffer);
|
||||
}
|
||||
} else if (c == FormatConversionCharInternal::a ||
|
||||
c == FormatConversionCharInternal::A) {
|
||||
bool uppercase = (c == FormatConversionCharInternal::A);
|
||||
FormatA(HexFloatTypeParams(Float{}), decomposed.mantissa,
|
||||
decomposed.exponent, uppercase, {sign_char, precision, conv, sink});
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
WriteBufferToSink(
|
||||
sign_char,
|
||||
absl::string_view(buffer.begin,
|
||||
static_cast<size_t>(buffer.end - buffer.begin)),
|
||||
conv, sink);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool ConvertFloatImpl(long double v, const FormatConversionSpecImpl &conv,
|
||||
FormatSinkImpl *sink) {
|
||||
if (IsDoubleDouble()) {
|
||||
// This is the `double-double` representation of `long double`. We do not
|
||||
// handle it natively. Fallback to snprintf.
|
||||
return FallbackToSnprintf(v, conv, sink);
|
||||
}
|
||||
|
||||
return FloatToSink(v, conv, sink);
|
||||
}
|
||||
|
||||
bool ConvertFloatImpl(float v, const FormatConversionSpecImpl &conv,
|
||||
FormatSinkImpl *sink) {
|
||||
return FloatToSink(static_cast<double>(v), conv, sink);
|
||||
}
|
||||
|
||||
bool ConvertFloatImpl(double v, const FormatConversionSpecImpl &conv,
|
||||
FormatSinkImpl *sink) {
|
||||
return FloatToSink(v, conv, sink);
|
||||
}
|
||||
|
||||
} // namespace str_format_internal
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
37
Pods/abseil/absl/strings/internal/str_format/float_conversion.h
generated
Normal file
37
Pods/abseil/absl/strings/internal/str_format/float_conversion.h
generated
Normal file
@@ -0,0 +1,37 @@
|
||||
// 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_
|
||||
|
||||
#include "absl/strings/internal/str_format/extension.h"
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace str_format_internal {
|
||||
|
||||
bool ConvertFloatImpl(float v, const FormatConversionSpecImpl &conv,
|
||||
FormatSinkImpl *sink);
|
||||
|
||||
bool ConvertFloatImpl(double v, const FormatConversionSpecImpl &conv,
|
||||
FormatSinkImpl *sink);
|
||||
|
||||
bool ConvertFloatImpl(long double v, const FormatConversionSpecImpl &conv,
|
||||
FormatSinkImpl *sink);
|
||||
|
||||
} // namespace str_format_internal
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
|
||||
#endif // ABSL_STRINGS_INTERNAL_STR_FORMAT_FLOAT_CONVERSION_H_
|
||||
72
Pods/abseil/absl/strings/internal/str_format/output.cc
generated
Normal file
72
Pods/abseil/absl/strings/internal/str_format/output.cc
generated
Normal file
@@ -0,0 +1,72 @@
|
||||
// Copyright 2017 The Abseil Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// 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/output.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <cstring>
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace str_format_internal {
|
||||
|
||||
namespace {
|
||||
struct ClearErrnoGuard {
|
||||
ClearErrnoGuard() : old_value(errno) { errno = 0; }
|
||||
~ClearErrnoGuard() {
|
||||
if (!errno) errno = old_value;
|
||||
}
|
||||
int old_value;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
void BufferRawSink::Write(string_view v) {
|
||||
size_t to_write = std::min(v.size(), size_);
|
||||
std::memcpy(buffer_, v.data(), to_write);
|
||||
buffer_ += to_write;
|
||||
size_ -= to_write;
|
||||
total_written_ += v.size();
|
||||
}
|
||||
|
||||
void FILERawSink::Write(string_view v) {
|
||||
while (!v.empty() && !error_) {
|
||||
// Reset errno to zero in case the libc implementation doesn't set errno
|
||||
// when a failure occurs.
|
||||
ClearErrnoGuard guard;
|
||||
|
||||
if (size_t result = std::fwrite(v.data(), 1, v.size(), output_)) {
|
||||
// Some progress was made.
|
||||
count_ += result;
|
||||
v.remove_prefix(result);
|
||||
} else {
|
||||
if (errno == EINTR) {
|
||||
continue;
|
||||
} else if (errno) {
|
||||
error_ = errno;
|
||||
} else if (std::ferror(output_)) {
|
||||
// Non-POSIX compliant libc implementations may not set errno, so we
|
||||
// have check the streams error indicator.
|
||||
error_ = EBADF;
|
||||
} else {
|
||||
// We're likely on a non-POSIX system that encountered EINTR but had no
|
||||
// way of reporting it.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace str_format_internal
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
97
Pods/abseil/absl/strings/internal/str_format/output.h
generated
Normal file
97
Pods/abseil/absl/strings/internal/str_format/output.h
generated
Normal file
@@ -0,0 +1,97 @@
|
||||
// Copyright 2017 The Abseil Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// Output extension hooks for the Format library.
|
||||
// `internal::InvokeFlush` calls the appropriate flush function for the
|
||||
// specified output argument.
|
||||
// `BufferRawSink` is a simple output sink for a char buffer. Used by SnprintF.
|
||||
// `FILERawSink` is a std::FILE* based sink. Used by PrintF and FprintF.
|
||||
|
||||
#ifndef ABSL_STRINGS_INTERNAL_STR_FORMAT_OUTPUT_H_
|
||||
#define ABSL_STRINGS_INTERNAL_STR_FORMAT_OUTPUT_H_
|
||||
|
||||
#include <cstdio>
|
||||
#include <ios>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
#include "absl/base/port.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace str_format_internal {
|
||||
|
||||
// RawSink implementation that writes into a char* buffer.
|
||||
// It will not overflow the buffer, but will keep the total count of chars
|
||||
// that would have been written.
|
||||
class BufferRawSink {
|
||||
public:
|
||||
BufferRawSink(char* buffer, size_t size) : buffer_(buffer), size_(size) {}
|
||||
|
||||
size_t total_written() const { return total_written_; }
|
||||
void Write(string_view v);
|
||||
|
||||
private:
|
||||
char* buffer_;
|
||||
size_t size_;
|
||||
size_t total_written_ = 0;
|
||||
};
|
||||
|
||||
// RawSink implementation that writes into a FILE*.
|
||||
// It keeps track of the total number of bytes written and any error encountered
|
||||
// during the writes.
|
||||
class FILERawSink {
|
||||
public:
|
||||
explicit FILERawSink(std::FILE* output) : output_(output) {}
|
||||
|
||||
void Write(string_view v);
|
||||
|
||||
size_t count() const { return count_; }
|
||||
int error() const { return error_; }
|
||||
|
||||
private:
|
||||
std::FILE* output_;
|
||||
int error_ = 0;
|
||||
size_t count_ = 0;
|
||||
};
|
||||
|
||||
// Provide RawSink integration with common types from the STL.
|
||||
inline void AbslFormatFlush(std::string* out, string_view s) {
|
||||
out->append(s.data(), s.size());
|
||||
}
|
||||
inline void AbslFormatFlush(std::ostream* out, string_view s) {
|
||||
out->write(s.data(), static_cast<std::streamsize>(s.size()));
|
||||
}
|
||||
|
||||
inline void AbslFormatFlush(FILERawSink* sink, string_view v) {
|
||||
sink->Write(v);
|
||||
}
|
||||
|
||||
inline void AbslFormatFlush(BufferRawSink* sink, string_view v) {
|
||||
sink->Write(v);
|
||||
}
|
||||
|
||||
// This is a SFINAE to get a better compiler error message when the type
|
||||
// is not supported.
|
||||
template <typename T>
|
||||
auto InvokeFlush(T* out, string_view s) -> decltype(AbslFormatFlush(out, s)) {
|
||||
AbslFormatFlush(out, s);
|
||||
}
|
||||
|
||||
} // namespace str_format_internal
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
|
||||
#endif // ABSL_STRINGS_INTERNAL_STR_FORMAT_OUTPUT_H_
|
||||
140
Pods/abseil/absl/strings/internal/str_format/parser.cc
generated
Normal file
140
Pods/abseil/absl/strings/internal/str_format/parser.cc
generated
Normal file
@@ -0,0 +1,140 @@
|
||||
// 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>
|
||||
#include <string.h>
|
||||
#include <wchar.h>
|
||||
#include <cctype>
|
||||
#include <cstdint>
|
||||
|
||||
#include <algorithm>
|
||||
#include <initializer_list>
|
||||
#include <limits>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace str_format_internal {
|
||||
|
||||
// Define the array for non-constexpr uses.
|
||||
constexpr ConvTag ConvTagHolder::value[256];
|
||||
|
||||
ABSL_ATTRIBUTE_NOINLINE const char* ConsumeUnboundConversionNoInline(
|
||||
const char* p, const char* end, UnboundConversion* conv, int* next_arg) {
|
||||
return ConsumeUnboundConversion(p, end, conv, next_arg);
|
||||
}
|
||||
|
||||
std::string LengthModToString(LengthMod v) {
|
||||
switch (v) {
|
||||
case LengthMod::h:
|
||||
return "h";
|
||||
case LengthMod::hh:
|
||||
return "hh";
|
||||
case LengthMod::l:
|
||||
return "l";
|
||||
case LengthMod::ll:
|
||||
return "ll";
|
||||
case LengthMod::L:
|
||||
return "L";
|
||||
case LengthMod::j:
|
||||
return "j";
|
||||
case LengthMod::z:
|
||||
return "z";
|
||||
case LengthMod::t:
|
||||
return "t";
|
||||
case LengthMod::q:
|
||||
return "q";
|
||||
case LengthMod::none:
|
||||
return "";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
struct ParsedFormatBase::ParsedFormatConsumer {
|
||||
explicit ParsedFormatConsumer(ParsedFormatBase *parsedformat)
|
||||
: parsed(parsedformat), data_pos(parsedformat->data_.get()) {}
|
||||
|
||||
bool Append(string_view s) {
|
||||
if (s.empty()) return true;
|
||||
|
||||
size_t text_end = AppendText(s);
|
||||
|
||||
if (!parsed->items_.empty() && !parsed->items_.back().is_conversion) {
|
||||
// Let's extend the existing text run.
|
||||
parsed->items_.back().text_end = text_end;
|
||||
} else {
|
||||
// Let's make a new text run.
|
||||
parsed->items_.push_back({false, text_end, {}});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ConvertOne(const UnboundConversion &conv, string_view s) {
|
||||
size_t text_end = AppendText(s);
|
||||
parsed->items_.push_back({true, text_end, conv});
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t AppendText(string_view s) {
|
||||
memcpy(data_pos, s.data(), s.size());
|
||||
data_pos += s.size();
|
||||
return static_cast<size_t>(data_pos - parsed->data_.get());
|
||||
}
|
||||
|
||||
ParsedFormatBase *parsed;
|
||||
char* data_pos;
|
||||
};
|
||||
|
||||
ParsedFormatBase::ParsedFormatBase(
|
||||
string_view format, bool allow_ignored,
|
||||
std::initializer_list<FormatConversionCharSet> convs)
|
||||
: data_(format.empty() ? nullptr : new char[format.size()]) {
|
||||
has_error_ = !ParseFormatString(format, ParsedFormatConsumer(this)) ||
|
||||
!MatchesConversions(allow_ignored, convs);
|
||||
}
|
||||
|
||||
bool ParsedFormatBase::MatchesConversions(
|
||||
bool allow_ignored,
|
||||
std::initializer_list<FormatConversionCharSet> convs) const {
|
||||
std::unordered_set<int> used;
|
||||
auto add_if_valid_conv = [&](int pos, char c) {
|
||||
if (static_cast<size_t>(pos) > convs.size() ||
|
||||
!Contains(convs.begin()[pos - 1], c))
|
||||
return false;
|
||||
used.insert(pos);
|
||||
return true;
|
||||
};
|
||||
for (const ConversionItem &item : items_) {
|
||||
if (!item.is_conversion) continue;
|
||||
auto &conv = item.conv;
|
||||
if (conv.precision.is_from_arg() &&
|
||||
!add_if_valid_conv(conv.precision.get_from_arg(), '*'))
|
||||
return false;
|
||||
if (conv.width.is_from_arg() &&
|
||||
!add_if_valid_conv(conv.width.get_from_arg(), '*'))
|
||||
return false;
|
||||
if (!add_if_valid_conv(conv.arg_position,
|
||||
FormatConversionCharToChar(conv.conv)))
|
||||
return false;
|
||||
}
|
||||
return used.size() == convs.size() || allow_ignored;
|
||||
}
|
||||
|
||||
} // namespace str_format_internal
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
269
Pods/abseil/absl/strings/internal/str_format/parser.h
generated
Normal file
269
Pods/abseil/absl/strings/internal/str_format/parser.h
generated
Normal file
@@ -0,0 +1,269 @@
|
||||
// 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_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <initializer_list>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/base/config.h"
|
||||
#include "absl/base/optimization.h"
|
||||
#include "absl/strings/internal/str_format/checker.h"
|
||||
#include "absl/strings/internal/str_format/constexpr_parser.h"
|
||||
#include "absl/strings/internal/str_format/extension.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace str_format_internal {
|
||||
|
||||
std::string LengthModToString(LengthMod v);
|
||||
|
||||
const char* ConsumeUnboundConversionNoInline(const char* p, const char* end,
|
||||
UnboundConversion* conv,
|
||||
int* next_arg);
|
||||
|
||||
// Parse the format string provided in 'src' and pass the identified items into
|
||||
// 'consumer'.
|
||||
// Text runs will be passed by calling
|
||||
// Consumer::Append(string_view);
|
||||
// ConversionItems will be passed by calling
|
||||
// Consumer::ConvertOne(UnboundConversion, string_view);
|
||||
// In the case of ConvertOne, the string_view that is passed is the
|
||||
// portion of the format string corresponding to the conversion, not including
|
||||
// the leading %. On success, it returns true. On failure, it stops and returns
|
||||
// false.
|
||||
template <typename Consumer>
|
||||
bool ParseFormatString(string_view src, Consumer consumer) {
|
||||
int next_arg = 0;
|
||||
const char* p = src.data();
|
||||
const char* const end = p + src.size();
|
||||
while (p != end) {
|
||||
const char* percent =
|
||||
static_cast<const char*>(memchr(p, '%', static_cast<size_t>(end - p)));
|
||||
if (!percent) {
|
||||
// We found the last substring.
|
||||
return consumer.Append(string_view(p, static_cast<size_t>(end - p)));
|
||||
}
|
||||
// We found a percent, so push the text run then process the percent.
|
||||
if (ABSL_PREDICT_FALSE(!consumer.Append(
|
||||
string_view(p, static_cast<size_t>(percent - p))))) {
|
||||
return false;
|
||||
}
|
||||
if (ABSL_PREDICT_FALSE(percent + 1 >= end)) return false;
|
||||
|
||||
auto tag = GetTagForChar(percent[1]);
|
||||
if (tag.is_conv()) {
|
||||
if (ABSL_PREDICT_FALSE(next_arg < 0)) {
|
||||
// This indicates an error in the format string.
|
||||
// The only way to get `next_arg < 0` here is to have a positional
|
||||
// argument first which sets next_arg to -1 and then a non-positional
|
||||
// argument.
|
||||
return false;
|
||||
}
|
||||
p = percent + 2;
|
||||
|
||||
// Keep this case separate from the one below.
|
||||
// ConvertOne is more efficient when the compiler can see that the `basic`
|
||||
// flag is set.
|
||||
UnboundConversion conv;
|
||||
conv.conv = tag.as_conv();
|
||||
conv.arg_position = ++next_arg;
|
||||
if (ABSL_PREDICT_FALSE(
|
||||
!consumer.ConvertOne(conv, string_view(percent + 1, 1)))) {
|
||||
return false;
|
||||
}
|
||||
} else if (percent[1] != '%') {
|
||||
UnboundConversion conv;
|
||||
p = ConsumeUnboundConversionNoInline(percent + 1, end, &conv, &next_arg);
|
||||
if (ABSL_PREDICT_FALSE(p == nullptr)) return false;
|
||||
if (ABSL_PREDICT_FALSE(!consumer.ConvertOne(
|
||||
conv, string_view(percent + 1,
|
||||
static_cast<size_t>(p - (percent + 1)))))) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (ABSL_PREDICT_FALSE(!consumer.Append("%"))) return false;
|
||||
p = percent + 2;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Always returns true, or fails to compile in a constexpr context if s does not
|
||||
// point to a constexpr char array.
|
||||
constexpr bool EnsureConstexpr(string_view s) {
|
||||
return s.empty() || s[0] == s[0];
|
||||
}
|
||||
|
||||
class ParsedFormatBase {
|
||||
public:
|
||||
explicit ParsedFormatBase(
|
||||
string_view format, bool allow_ignored,
|
||||
std::initializer_list<FormatConversionCharSet> convs);
|
||||
|
||||
ParsedFormatBase(const ParsedFormatBase& other) { *this = other; }
|
||||
|
||||
ParsedFormatBase(ParsedFormatBase&& other) { *this = std::move(other); }
|
||||
|
||||
ParsedFormatBase& operator=(const ParsedFormatBase& other) {
|
||||
if (this == &other) return *this;
|
||||
has_error_ = other.has_error_;
|
||||
items_ = other.items_;
|
||||
size_t text_size = items_.empty() ? 0 : items_.back().text_end;
|
||||
data_.reset(new char[text_size]);
|
||||
memcpy(data_.get(), other.data_.get(), text_size);
|
||||
return *this;
|
||||
}
|
||||
|
||||
ParsedFormatBase& operator=(ParsedFormatBase&& other) {
|
||||
if (this == &other) return *this;
|
||||
has_error_ = other.has_error_;
|
||||
data_ = std::move(other.data_);
|
||||
items_ = std::move(other.items_);
|
||||
// Reset the vector to make sure the invariants hold.
|
||||
other.items_.clear();
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename Consumer>
|
||||
bool ProcessFormat(Consumer consumer) const {
|
||||
const char* const base = data_.get();
|
||||
string_view text(base, 0);
|
||||
for (const auto& item : items_) {
|
||||
const char* const end = text.data() + text.size();
|
||||
text =
|
||||
string_view(end, static_cast<size_t>((base + item.text_end) - end));
|
||||
if (item.is_conversion) {
|
||||
if (!consumer.ConvertOne(item.conv, text)) return false;
|
||||
} else {
|
||||
if (!consumer.Append(text)) return false;
|
||||
}
|
||||
}
|
||||
return !has_error_;
|
||||
}
|
||||
|
||||
bool has_error() const { return has_error_; }
|
||||
|
||||
private:
|
||||
// Returns whether the conversions match and if !allow_ignored it verifies
|
||||
// that all conversions are used by the format.
|
||||
bool MatchesConversions(
|
||||
bool allow_ignored,
|
||||
std::initializer_list<FormatConversionCharSet> convs) const;
|
||||
|
||||
struct ParsedFormatConsumer;
|
||||
|
||||
struct ConversionItem {
|
||||
bool is_conversion;
|
||||
// Points to the past-the-end location of this element in the data_ array.
|
||||
size_t text_end;
|
||||
UnboundConversion conv;
|
||||
};
|
||||
|
||||
bool has_error_;
|
||||
std::unique_ptr<char[]> data_;
|
||||
std::vector<ConversionItem> items_;
|
||||
};
|
||||
|
||||
|
||||
// A value type representing a preparsed format. These can be created, copied
|
||||
// around, and reused to speed up formatting loops.
|
||||
// The user must specify through the template arguments the conversion
|
||||
// characters used in the format. This will be checked at compile time.
|
||||
//
|
||||
// This class uses Conv enum values to specify each argument.
|
||||
// This allows for more flexibility as you can specify multiple possible
|
||||
// conversion characters for each argument.
|
||||
// ParsedFormat<char...> is a simplified alias for when the user only
|
||||
// needs to specify a single conversion character for each argument.
|
||||
//
|
||||
// Example:
|
||||
// // Extended format supports multiple characters per argument:
|
||||
// using MyFormat = ExtendedParsedFormat<Conv::d | Conv::x>;
|
||||
// MyFormat GetFormat(bool use_hex) {
|
||||
// if (use_hex) return MyFormat("foo %x bar");
|
||||
// return MyFormat("foo %d bar");
|
||||
// }
|
||||
// // 'format' can be used with any value that supports 'd' and 'x',
|
||||
// // like `int`.
|
||||
// auto format = GetFormat(use_hex);
|
||||
// value = StringF(format, i);
|
||||
//
|
||||
// This class also supports runtime format checking with the ::New() and
|
||||
// ::NewAllowIgnored() factory functions.
|
||||
// This is the only API that allows the user to pass a runtime specified format
|
||||
// string. These factory functions will return NULL if the format does not match
|
||||
// the conversions requested by the user.
|
||||
template <FormatConversionCharSet... C>
|
||||
class ExtendedParsedFormat : public str_format_internal::ParsedFormatBase {
|
||||
public:
|
||||
explicit ExtendedParsedFormat(string_view format)
|
||||
#ifdef ABSL_INTERNAL_ENABLE_FORMAT_CHECKER
|
||||
__attribute__((
|
||||
enable_if(str_format_internal::EnsureConstexpr(format),
|
||||
"Format string is not constexpr."),
|
||||
enable_if(str_format_internal::ValidFormatImpl<C...>(format),
|
||||
"Format specified does not match the template arguments.")))
|
||||
#endif // ABSL_INTERNAL_ENABLE_FORMAT_CHECKER
|
||||
: ExtendedParsedFormat(format, false) {
|
||||
}
|
||||
|
||||
// ExtendedParsedFormat factory function.
|
||||
// The user still has to specify the conversion characters, but they will not
|
||||
// be checked at compile time. Instead, it will be checked at runtime.
|
||||
// This delays the checking to runtime, but allows the user to pass
|
||||
// dynamically sourced formats.
|
||||
// It returns NULL if the format does not match the conversion characters.
|
||||
// The user is responsible for checking the return value before using it.
|
||||
//
|
||||
// The 'New' variant will check that all the specified arguments are being
|
||||
// consumed by the format and return NULL if any argument is being ignored.
|
||||
// The 'NewAllowIgnored' variant will not verify this and will allow formats
|
||||
// that ignore arguments.
|
||||
static std::unique_ptr<ExtendedParsedFormat> New(string_view format) {
|
||||
return New(format, false);
|
||||
}
|
||||
static std::unique_ptr<ExtendedParsedFormat> NewAllowIgnored(
|
||||
string_view format) {
|
||||
return New(format, true);
|
||||
}
|
||||
|
||||
private:
|
||||
static std::unique_ptr<ExtendedParsedFormat> New(string_view format,
|
||||
bool allow_ignored) {
|
||||
std::unique_ptr<ExtendedParsedFormat> conv(
|
||||
new ExtendedParsedFormat(format, allow_ignored));
|
||||
if (conv->has_error()) return nullptr;
|
||||
return conv;
|
||||
}
|
||||
|
||||
ExtendedParsedFormat(string_view s, bool allow_ignored)
|
||||
: ParsedFormatBase(s, allow_ignored, {C...}) {}
|
||||
};
|
||||
} // namespace str_format_internal
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
|
||||
#endif // ABSL_STRINGS_INTERNAL_STR_FORMAT_PARSER_H_
|
||||
317
Pods/abseil/absl/strings/internal/str_join_internal.h
generated
Normal file
317
Pods/abseil/absl/strings/internal/str_join_internal.h
generated
Normal file
@@ -0,0 +1,317 @@
|
||||
//
|
||||
// Copyright 2017 The Abseil Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
// This file declares INTERNAL parts of the Join API that are inlined/templated
|
||||
// or otherwise need to be available at compile time. The main abstractions
|
||||
// defined in this file are:
|
||||
//
|
||||
// - A handful of default Formatters
|
||||
// - JoinAlgorithm() overloads
|
||||
// - JoinRange() overloads
|
||||
// - JoinTuple()
|
||||
//
|
||||
// DO NOT INCLUDE THIS FILE DIRECTLY. Use this file by including
|
||||
// absl/strings/str_join.h
|
||||
//
|
||||
// IWYU pragma: private, include "absl/strings/str_join.h"
|
||||
|
||||
#ifndef ABSL_STRINGS_INTERNAL_STR_JOIN_INTERNAL_H_
|
||||
#define ABSL_STRINGS_INTERNAL_STR_JOIN_INTERNAL_H_
|
||||
|
||||
#include <cstring>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "absl/strings/internal/ostringstream.h"
|
||||
#include "absl/strings/internal/resize_uninitialized.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace strings_internal {
|
||||
|
||||
//
|
||||
// Formatter objects
|
||||
//
|
||||
// The following are implementation classes for standard Formatter objects. The
|
||||
// factory functions that users will call to create and use these formatters are
|
||||
// defined and documented in strings/join.h.
|
||||
//
|
||||
|
||||
// The default formatter. Converts alpha-numeric types to strings.
|
||||
struct AlphaNumFormatterImpl {
|
||||
// This template is needed in order to support passing in a dereferenced
|
||||
// vector<bool>::iterator
|
||||
template <typename T>
|
||||
void operator()(std::string* out, const T& t) const {
|
||||
StrAppend(out, AlphaNum(t));
|
||||
}
|
||||
|
||||
void operator()(std::string* out, const AlphaNum& t) const {
|
||||
StrAppend(out, t);
|
||||
}
|
||||
};
|
||||
|
||||
// A type that's used to overload the JoinAlgorithm() function (defined below)
|
||||
// for ranges that do not require additional formatting (e.g., a range of
|
||||
// strings).
|
||||
|
||||
struct NoFormatter : public AlphaNumFormatterImpl {};
|
||||
|
||||
// Formats types to strings using the << operator.
|
||||
class StreamFormatterImpl {
|
||||
public:
|
||||
// The method isn't const because it mutates state. Making it const will
|
||||
// render StreamFormatterImpl thread-hostile.
|
||||
template <typename T>
|
||||
void operator()(std::string* out, const T& t) {
|
||||
// The stream is created lazily to avoid paying the relatively high cost
|
||||
// of its construction when joining an empty range.
|
||||
if (strm_) {
|
||||
strm_->clear(); // clear the bad, fail and eof bits in case they were set
|
||||
strm_->str(out);
|
||||
} else {
|
||||
strm_.reset(new strings_internal::OStringStream(out));
|
||||
}
|
||||
*strm_ << t;
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<strings_internal::OStringStream> strm_;
|
||||
};
|
||||
|
||||
// Formats a std::pair<>. The 'first' member is formatted using f1_ and the
|
||||
// 'second' member is formatted using f2_. sep_ is the separator.
|
||||
template <typename F1, typename F2>
|
||||
class PairFormatterImpl {
|
||||
public:
|
||||
PairFormatterImpl(F1 f1, absl::string_view sep, F2 f2)
|
||||
: f1_(std::move(f1)), sep_(sep), f2_(std::move(f2)) {}
|
||||
|
||||
template <typename T>
|
||||
void operator()(std::string* out, const T& p) {
|
||||
f1_(out, p.first);
|
||||
out->append(sep_);
|
||||
f2_(out, p.second);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void operator()(std::string* out, const T& p) const {
|
||||
f1_(out, p.first);
|
||||
out->append(sep_);
|
||||
f2_(out, p.second);
|
||||
}
|
||||
|
||||
private:
|
||||
F1 f1_;
|
||||
std::string sep_;
|
||||
F2 f2_;
|
||||
};
|
||||
|
||||
// Wraps another formatter and dereferences the argument to operator() then
|
||||
// passes the dereferenced argument to the wrapped formatter. This can be
|
||||
// useful, for example, to join a std::vector<int*>.
|
||||
template <typename Formatter>
|
||||
class DereferenceFormatterImpl {
|
||||
public:
|
||||
DereferenceFormatterImpl() : f_() {}
|
||||
explicit DereferenceFormatterImpl(Formatter&& f)
|
||||
: f_(std::forward<Formatter>(f)) {}
|
||||
|
||||
template <typename T>
|
||||
void operator()(std::string* out, const T& t) {
|
||||
f_(out, *t);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void operator()(std::string* out, const T& t) const {
|
||||
f_(out, *t);
|
||||
}
|
||||
|
||||
private:
|
||||
Formatter f_;
|
||||
};
|
||||
|
||||
// DefaultFormatter<T> is a traits class that selects a default Formatter to use
|
||||
// for the given type T. The ::Type member names the Formatter to use. This is
|
||||
// used by the strings::Join() functions that do NOT take a Formatter argument,
|
||||
// in which case a default Formatter must be chosen.
|
||||
//
|
||||
// AlphaNumFormatterImpl is the default in the base template, followed by
|
||||
// specializations for other types.
|
||||
template <typename ValueType>
|
||||
struct DefaultFormatter {
|
||||
typedef AlphaNumFormatterImpl Type;
|
||||
};
|
||||
template <>
|
||||
struct DefaultFormatter<const char*> {
|
||||
typedef AlphaNumFormatterImpl Type;
|
||||
};
|
||||
template <>
|
||||
struct DefaultFormatter<char*> {
|
||||
typedef AlphaNumFormatterImpl Type;
|
||||
};
|
||||
template <>
|
||||
struct DefaultFormatter<std::string> {
|
||||
typedef NoFormatter Type;
|
||||
};
|
||||
template <>
|
||||
struct DefaultFormatter<absl::string_view> {
|
||||
typedef NoFormatter Type;
|
||||
};
|
||||
template <typename ValueType>
|
||||
struct DefaultFormatter<ValueType*> {
|
||||
typedef DereferenceFormatterImpl<typename DefaultFormatter<ValueType>::Type>
|
||||
Type;
|
||||
};
|
||||
|
||||
template <typename ValueType>
|
||||
struct DefaultFormatter<std::unique_ptr<ValueType>>
|
||||
: public DefaultFormatter<ValueType*> {};
|
||||
|
||||
//
|
||||
// JoinAlgorithm() functions
|
||||
//
|
||||
|
||||
// The main joining algorithm. This simply joins the elements in the given
|
||||
// iterator range, each separated by the given separator, into an output string,
|
||||
// and formats each element using the provided Formatter object.
|
||||
template <typename Iterator, typename Formatter>
|
||||
std::string JoinAlgorithm(Iterator start, Iterator end, absl::string_view s,
|
||||
Formatter&& f) {
|
||||
std::string result;
|
||||
absl::string_view sep("");
|
||||
for (Iterator it = start; it != end; ++it) {
|
||||
result.append(sep.data(), sep.size());
|
||||
f(&result, *it);
|
||||
sep = s;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// A joining algorithm that's optimized for a forward iterator range of
|
||||
// string-like objects that do not need any additional formatting. This is to
|
||||
// optimize the common case of joining, say, a std::vector<string> or a
|
||||
// std::vector<absl::string_view>.
|
||||
//
|
||||
// This is an overload of the previous JoinAlgorithm() function. Here the
|
||||
// Formatter argument is of type NoFormatter. Since NoFormatter is an internal
|
||||
// type, this overload is only invoked when strings::Join() is called with a
|
||||
// range of string-like objects (e.g., std::string, absl::string_view), and an
|
||||
// explicit Formatter argument was NOT specified.
|
||||
//
|
||||
// The optimization is that the needed space will be reserved in the output
|
||||
// string to avoid the need to resize while appending. To do this, the iterator
|
||||
// range will be traversed twice: once to calculate the total needed size, and
|
||||
// then again to copy the elements and delimiters to the output string.
|
||||
template <typename Iterator,
|
||||
typename = typename std::enable_if<std::is_convertible<
|
||||
typename std::iterator_traits<Iterator>::iterator_category,
|
||||
std::forward_iterator_tag>::value>::type>
|
||||
std::string JoinAlgorithm(Iterator start, Iterator end, absl::string_view s,
|
||||
NoFormatter) {
|
||||
std::string result;
|
||||
if (start != end) {
|
||||
// Sums size
|
||||
auto&& start_value = *start;
|
||||
size_t result_size = start_value.size();
|
||||
for (Iterator it = start; ++it != end;) {
|
||||
result_size += s.size();
|
||||
result_size += (*it).size();
|
||||
}
|
||||
|
||||
if (result_size > 0) {
|
||||
STLStringResizeUninitialized(&result, result_size);
|
||||
|
||||
// Joins strings
|
||||
char* result_buf = &*result.begin();
|
||||
|
||||
memcpy(result_buf, start_value.data(), start_value.size());
|
||||
result_buf += start_value.size();
|
||||
for (Iterator it = start; ++it != end;) {
|
||||
memcpy(result_buf, s.data(), s.size());
|
||||
result_buf += s.size();
|
||||
auto&& value = *it;
|
||||
memcpy(result_buf, value.data(), value.size());
|
||||
result_buf += value.size();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// JoinTupleLoop implements a loop over the elements of a std::tuple, which
|
||||
// are heterogeneous. The primary template matches the tuple interior case. It
|
||||
// continues the iteration after appending a separator (for nonzero indices)
|
||||
// and formatting an element of the tuple. The specialization for the I=N case
|
||||
// matches the end-of-tuple, and terminates the iteration.
|
||||
template <size_t I, size_t N>
|
||||
struct JoinTupleLoop {
|
||||
template <typename Tup, typename Formatter>
|
||||
void operator()(std::string* out, const Tup& tup, absl::string_view sep,
|
||||
Formatter&& fmt) {
|
||||
if (I > 0) out->append(sep.data(), sep.size());
|
||||
fmt(out, std::get<I>(tup));
|
||||
JoinTupleLoop<I + 1, N>()(out, tup, sep, fmt);
|
||||
}
|
||||
};
|
||||
template <size_t N>
|
||||
struct JoinTupleLoop<N, N> {
|
||||
template <typename Tup, typename Formatter>
|
||||
void operator()(std::string*, const Tup&, absl::string_view, Formatter&&) {}
|
||||
};
|
||||
|
||||
template <typename... T, typename Formatter>
|
||||
std::string JoinAlgorithm(const std::tuple<T...>& tup, absl::string_view sep,
|
||||
Formatter&& fmt) {
|
||||
std::string result;
|
||||
JoinTupleLoop<0, sizeof...(T)>()(&result, tup, sep, fmt);
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
std::string JoinRange(Iterator first, Iterator last,
|
||||
absl::string_view separator) {
|
||||
// No formatter was explicitly given, so a default must be chosen.
|
||||
typedef typename std::iterator_traits<Iterator>::value_type ValueType;
|
||||
typedef typename DefaultFormatter<ValueType>::Type Formatter;
|
||||
return JoinAlgorithm(first, last, separator, Formatter());
|
||||
}
|
||||
|
||||
template <typename Range, typename Formatter>
|
||||
std::string JoinRange(const Range& range, absl::string_view separator,
|
||||
Formatter&& fmt) {
|
||||
using std::begin;
|
||||
using std::end;
|
||||
return JoinAlgorithm(begin(range), end(range), separator, fmt);
|
||||
}
|
||||
|
||||
template <typename Range>
|
||||
std::string JoinRange(const Range& range, absl::string_view separator) {
|
||||
using std::begin;
|
||||
using std::end;
|
||||
return JoinRange(begin(range), end(range), separator);
|
||||
}
|
||||
|
||||
} // namespace strings_internal
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
|
||||
#endif // ABSL_STRINGS_INTERNAL_STR_JOIN_INTERNAL_H_
|
||||
481
Pods/abseil/absl/strings/internal/str_split_internal.h
generated
Normal file
481
Pods/abseil/absl/strings/internal/str_split_internal.h
generated
Normal file
@@ -0,0 +1,481 @@
|
||||
// Copyright 2017 The Abseil Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
// This file declares INTERNAL parts of the Split API that are inline/templated
|
||||
// or otherwise need to be available at compile time. The main abstractions
|
||||
// defined in here are
|
||||
//
|
||||
// - ConvertibleToStringView
|
||||
// - SplitIterator<>
|
||||
// - Splitter<>
|
||||
//
|
||||
// DO NOT INCLUDE THIS FILE DIRECTLY. Use this file by including
|
||||
// absl/strings/str_split.h.
|
||||
//
|
||||
// IWYU pragma: private, include "absl/strings/str_split.h"
|
||||
|
||||
#ifndef ABSL_STRINGS_INTERNAL_STR_SPLIT_INTERNAL_H_
|
||||
#define ABSL_STRINGS_INTERNAL_STR_SPLIT_INTERNAL_H_
|
||||
|
||||
#include <array>
|
||||
#include <initializer_list>
|
||||
#include <iterator>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/base/macros.h"
|
||||
#include "absl/base/port.h"
|
||||
#include "absl/meta/type_traits.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
|
||||
#ifdef _GLIBCXX_DEBUG
|
||||
#include "absl/strings/internal/stl_type_traits.h"
|
||||
#endif // _GLIBCXX_DEBUG
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace strings_internal {
|
||||
|
||||
// This class is implicitly constructible from everything that absl::string_view
|
||||
// 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)
|
||||
: value_(s) {}
|
||||
ConvertibleToStringView(char* s) : value_(s) {} // NOLINT(runtime/explicit)
|
||||
ConvertibleToStringView(absl::string_view s) // NOLINT(runtime/explicit)
|
||||
: value_(s) {}
|
||||
ConvertibleToStringView(const std::string& s) // NOLINT(runtime/explicit)
|
||||
: value_(s) {}
|
||||
|
||||
// Disable conversion from rvalue strings.
|
||||
ConvertibleToStringView(std::string&& s) = delete;
|
||||
ConvertibleToStringView(const std::string&& s) = delete;
|
||||
|
||||
absl::string_view value() const { return value_; }
|
||||
|
||||
private:
|
||||
absl::string_view value_;
|
||||
};
|
||||
|
||||
// An iterator that enumerates the parts of a string from a Splitter. The text
|
||||
// to be split, the Delimiter, and the Predicate are all taken from the given
|
||||
// Splitter object. Iterators may only be compared if they refer to the same
|
||||
// Splitter instance.
|
||||
//
|
||||
// This class is NOT part of the public splitting API.
|
||||
template <typename Splitter>
|
||||
class SplitIterator {
|
||||
public:
|
||||
using iterator_category = std::input_iterator_tag;
|
||||
using value_type = absl::string_view;
|
||||
using difference_type = ptrdiff_t;
|
||||
using pointer = const value_type*;
|
||||
using reference = const value_type&;
|
||||
|
||||
enum State { kInitState, kLastState, kEndState };
|
||||
SplitIterator(State state, const Splitter* splitter)
|
||||
: pos_(0),
|
||||
state_(state),
|
||||
splitter_(splitter),
|
||||
delimiter_(splitter->delimiter()),
|
||||
predicate_(splitter->predicate()) {
|
||||
// Hack to maintain backward compatibility. This one block makes it so an
|
||||
// empty absl::string_view whose .data() happens to be nullptr behaves
|
||||
// *differently* from an otherwise empty absl::string_view whose .data() is
|
||||
// not nullptr. This is an undesirable difference in general, but this
|
||||
// behavior is maintained to avoid breaking existing code that happens to
|
||||
// depend on this old behavior/bug. Perhaps it will be fixed one day. The
|
||||
// difference in behavior is as follows:
|
||||
// Split(absl::string_view(""), '-'); // {""}
|
||||
// Split(absl::string_view(), '-'); // {}
|
||||
if (splitter_->text().data() == nullptr) {
|
||||
state_ = kEndState;
|
||||
pos_ = splitter_->text().size();
|
||||
return;
|
||||
}
|
||||
|
||||
if (state_ == kEndState) {
|
||||
pos_ = splitter_->text().size();
|
||||
} else {
|
||||
++(*this);
|
||||
}
|
||||
}
|
||||
|
||||
bool at_end() const { return state_ == kEndState; }
|
||||
|
||||
reference operator*() const { return curr_; }
|
||||
pointer operator->() const { return &curr_; }
|
||||
|
||||
SplitIterator& operator++() {
|
||||
do {
|
||||
if (state_ == kLastState) {
|
||||
state_ = kEndState;
|
||||
return *this;
|
||||
}
|
||||
const absl::string_view text = splitter_->text();
|
||||
const absl::string_view d = delimiter_.Find(text, pos_);
|
||||
if (d.data() == text.data() + text.size()) state_ = kLastState;
|
||||
curr_ = text.substr(pos_,
|
||||
static_cast<size_t>(d.data() - (text.data() + pos_)));
|
||||
pos_ += curr_.size() + d.size();
|
||||
} while (!predicate_(curr_));
|
||||
return *this;
|
||||
}
|
||||
|
||||
SplitIterator operator++(int) {
|
||||
SplitIterator old(*this);
|
||||
++(*this);
|
||||
return old;
|
||||
}
|
||||
|
||||
friend bool operator==(const SplitIterator& a, const SplitIterator& b) {
|
||||
return a.state_ == b.state_ && a.pos_ == b.pos_;
|
||||
}
|
||||
|
||||
friend bool operator!=(const SplitIterator& a, const SplitIterator& b) {
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
private:
|
||||
size_t pos_;
|
||||
State state_;
|
||||
absl::string_view curr_;
|
||||
const Splitter* splitter_;
|
||||
typename Splitter::DelimiterType delimiter_;
|
||||
typename Splitter::PredicateType predicate_;
|
||||
};
|
||||
|
||||
// HasMappedType<T>::value is true iff there exists a type T::mapped_type.
|
||||
template <typename T, typename = void>
|
||||
struct HasMappedType : std::false_type {};
|
||||
template <typename T>
|
||||
struct HasMappedType<T, absl::void_t<typename T::mapped_type>>
|
||||
: std::true_type {};
|
||||
|
||||
// HasValueType<T>::value is true iff there exists a type T::value_type.
|
||||
template <typename T, typename = void>
|
||||
struct HasValueType : std::false_type {};
|
||||
template <typename T>
|
||||
struct HasValueType<T, absl::void_t<typename T::value_type>> : std::true_type {
|
||||
};
|
||||
|
||||
// HasConstIterator<T>::value is true iff there exists a type T::const_iterator.
|
||||
template <typename T, typename = void>
|
||||
struct HasConstIterator : std::false_type {};
|
||||
template <typename T>
|
||||
struct HasConstIterator<T, absl::void_t<typename T::const_iterator>>
|
||||
: std::true_type {};
|
||||
|
||||
// HasEmplace<T>::value is true iff there exists a method T::emplace().
|
||||
template <typename T, typename = void>
|
||||
struct HasEmplace : std::false_type {};
|
||||
template <typename T>
|
||||
struct HasEmplace<T, absl::void_t<decltype(std::declval<T>().emplace())>>
|
||||
: std::true_type {};
|
||||
|
||||
// IsInitializerList<T>::value is true iff T is an std::initializer_list. More
|
||||
// details below in Splitter<> where this is used.
|
||||
std::false_type IsInitializerListDispatch(...); // default: No
|
||||
template <typename T>
|
||||
std::true_type IsInitializerListDispatch(std::initializer_list<T>*);
|
||||
template <typename T>
|
||||
struct IsInitializerList
|
||||
: decltype(IsInitializerListDispatch(static_cast<T*>(nullptr))) {};
|
||||
|
||||
// A SplitterIsConvertibleTo<C>::type alias exists iff the specified condition
|
||||
// is true for type 'C'.
|
||||
//
|
||||
// Restricts conversion to container-like types (by testing for the presence of
|
||||
// a const_iterator member type) and also to disable conversion to an
|
||||
// std::initializer_list (which also has a const_iterator). Otherwise, code
|
||||
// compiled in C++11 will get an error due to ambiguous conversion paths (in
|
||||
// C++11 std::vector<T>::operator= is overloaded to take either a std::vector<T>
|
||||
// or an std::initializer_list<T>).
|
||||
|
||||
template <typename C, bool has_value_type, bool has_mapped_type>
|
||||
struct SplitterIsConvertibleToImpl : std::false_type {};
|
||||
|
||||
template <typename C>
|
||||
struct SplitterIsConvertibleToImpl<C, true, false>
|
||||
: std::is_constructible<typename C::value_type, absl::string_view> {};
|
||||
|
||||
template <typename C>
|
||||
struct SplitterIsConvertibleToImpl<C, true, true>
|
||||
: absl::conjunction<
|
||||
std::is_constructible<typename C::key_type, absl::string_view>,
|
||||
std::is_constructible<typename C::mapped_type, absl::string_view>> {};
|
||||
|
||||
template <typename C>
|
||||
struct SplitterIsConvertibleTo
|
||||
: SplitterIsConvertibleToImpl<
|
||||
C,
|
||||
#ifdef _GLIBCXX_DEBUG
|
||||
!IsStrictlyBaseOfAndConvertibleToSTLContainer<C>::value &&
|
||||
#endif // _GLIBCXX_DEBUG
|
||||
!IsInitializerList<
|
||||
typename std::remove_reference<C>::type>::value &&
|
||||
HasValueType<C>::value && HasConstIterator<C>::value,
|
||||
HasMappedType<C>::value> {
|
||||
};
|
||||
|
||||
template <typename StringType, typename Container, typename = void>
|
||||
struct ShouldUseLifetimeBound : std::false_type {};
|
||||
|
||||
template <typename StringType, typename Container>
|
||||
struct ShouldUseLifetimeBound<
|
||||
StringType, Container,
|
||||
std::enable_if_t<
|
||||
std::is_same<StringType, std::string>::value &&
|
||||
std::is_same<typename Container::value_type, absl::string_view>::value>>
|
||||
: std::true_type {};
|
||||
|
||||
template <typename StringType, typename First, typename Second>
|
||||
using ShouldUseLifetimeBoundForPair = std::integral_constant<
|
||||
bool, std::is_same<StringType, std::string>::value &&
|
||||
(std::is_same<First, absl::string_view>::value ||
|
||||
std::is_same<Second, absl::string_view>::value)>;
|
||||
|
||||
|
||||
// This class implements the range that is returned by absl::StrSplit(). This
|
||||
// class has templated conversion operators that allow it to be implicitly
|
||||
// converted to a variety of types that the caller may have specified on the
|
||||
// left-hand side of an assignment.
|
||||
//
|
||||
// The main interface for interacting with this class is through its implicit
|
||||
// conversion operators. However, this class may also be used like a container
|
||||
// in that it has .begin() and .end() member functions. It may also be used
|
||||
// within a range-for loop.
|
||||
//
|
||||
// Output containers can be collections of any type that is constructible from
|
||||
// an absl::string_view.
|
||||
//
|
||||
// An Predicate functor may be supplied. This predicate will be used to filter
|
||||
// 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.
|
||||
//
|
||||
// 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;
|
||||
using PredicateType = Predicate;
|
||||
using const_iterator = strings_internal::SplitIterator<Splitter>;
|
||||
using value_type = typename std::iterator_traits<const_iterator>::value_type;
|
||||
|
||||
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_; }
|
||||
const Delimiter& delimiter() const { return delimiter_; }
|
||||
const Predicate& predicate() const { return predicate_; }
|
||||
|
||||
// Range functions that iterate the split substrings as absl::string_view
|
||||
// objects. These methods enable a Splitter to be used in a range-based for
|
||||
// loop.
|
||||
const_iterator begin() const { return {const_iterator::kInitState, this}; }
|
||||
const_iterator end() const { return {const_iterator::kEndState, this}; }
|
||||
|
||||
// An implicit conversion operator that is restricted to only those containers
|
||||
// that the splitter is convertible to.
|
||||
template <
|
||||
typename Container,
|
||||
std::enable_if_t<ShouldUseLifetimeBound<StringType, Container>::value &&
|
||||
SplitterIsConvertibleTo<Container>::value,
|
||||
std::nullptr_t> = nullptr>
|
||||
// NOLINTNEXTLINE(google-explicit-constructor)
|
||||
operator Container() const ABSL_ATTRIBUTE_LIFETIME_BOUND {
|
||||
return ConvertToContainer<Container, typename Container::value_type,
|
||||
HasMappedType<Container>::value>()(*this);
|
||||
}
|
||||
|
||||
template <
|
||||
typename Container,
|
||||
std::enable_if_t<!ShouldUseLifetimeBound<StringType, Container>::value &&
|
||||
SplitterIsConvertibleTo<Container>::value,
|
||||
std::nullptr_t> = nullptr>
|
||||
// NOLINTNEXTLINE(google-explicit-constructor)
|
||||
operator Container() const {
|
||||
return ConvertToContainer<Container, typename Container::value_type,
|
||||
HasMappedType<Container>::value>()(*this);
|
||||
}
|
||||
|
||||
// Returns a pair with its .first and .second members set to the first two
|
||||
// strings returned by the begin() iterator. Either/both of .first and .second
|
||||
// will be constructed with empty strings if the iterator doesn't have a
|
||||
// corresponding value.
|
||||
template <typename First, typename Second,
|
||||
std::enable_if_t<
|
||||
ShouldUseLifetimeBoundForPair<StringType, First, Second>::value,
|
||||
std::nullptr_t> = nullptr>
|
||||
// NOLINTNEXTLINE(google-explicit-constructor)
|
||||
operator std::pair<First, Second>() const ABSL_ATTRIBUTE_LIFETIME_BOUND {
|
||||
return ConvertToPair<First, Second>();
|
||||
}
|
||||
|
||||
template <typename First, typename Second,
|
||||
std::enable_if_t<!ShouldUseLifetimeBoundForPair<StringType, First,
|
||||
Second>::value,
|
||||
std::nullptr_t> = nullptr>
|
||||
// NOLINTNEXTLINE(google-explicit-constructor)
|
||||
operator std::pair<First, Second>() const {
|
||||
return ConvertToPair<First, Second>();
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename First, typename Second>
|
||||
std::pair<First, Second> ConvertToPair() const {
|
||||
absl::string_view first, second;
|
||||
auto it = begin();
|
||||
if (it != end()) {
|
||||
first = *it;
|
||||
if (++it != end()) {
|
||||
second = *it;
|
||||
}
|
||||
}
|
||||
return {First(first), Second(second)};
|
||||
}
|
||||
|
||||
// ConvertToContainer is a functor converting a Splitter to the requested
|
||||
// Container of ValueType. It is specialized below to optimize splitting to
|
||||
// certain combinations of Container and ValueType.
|
||||
//
|
||||
// This base template handles the generic case of storing the split results in
|
||||
// the requested non-map-like container and converting the split substrings to
|
||||
// the requested type.
|
||||
template <typename Container, typename ValueType, bool is_map = false>
|
||||
struct ConvertToContainer {
|
||||
Container operator()(const Splitter& splitter) const {
|
||||
Container c;
|
||||
auto it = std::inserter(c, c.end());
|
||||
for (const auto& sp : splitter) {
|
||||
*it++ = ValueType(sp);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
};
|
||||
|
||||
// Partial specialization for a std::vector<absl::string_view>.
|
||||
//
|
||||
// Optimized for the common case of splitting to a
|
||||
// std::vector<absl::string_view>. In this case we first split the results to
|
||||
// a small array of absl::string_view on the stack, to reduce reallocations.
|
||||
template <typename A>
|
||||
struct ConvertToContainer<std::vector<absl::string_view, A>,
|
||||
absl::string_view, false> {
|
||||
std::vector<absl::string_view, A> operator()(
|
||||
const Splitter& splitter) const {
|
||||
struct raw_view {
|
||||
const char* data;
|
||||
size_t size;
|
||||
operator absl::string_view() const { // NOLINT(runtime/explicit)
|
||||
return {data, size};
|
||||
}
|
||||
};
|
||||
std::vector<absl::string_view, A> v;
|
||||
std::array<raw_view, 16> ar;
|
||||
for (auto it = splitter.begin(); !it.at_end();) {
|
||||
size_t index = 0;
|
||||
do {
|
||||
ar[index].data = it->data();
|
||||
ar[index].size = it->size();
|
||||
++it;
|
||||
} while (++index != ar.size() && !it.at_end());
|
||||
v.insert(v.end(), ar.begin(), ar.begin() + index);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
};
|
||||
|
||||
// Partial specialization for a std::vector<std::string>.
|
||||
//
|
||||
// Optimized for the common case of splitting to a std::vector<std::string>.
|
||||
// In this case we first split the results to a std::vector<absl::string_view>
|
||||
// so the returned std::vector<std::string> can have space reserved to avoid
|
||||
// std::string moves.
|
||||
template <typename A>
|
||||
struct ConvertToContainer<std::vector<std::string, A>, std::string, false> {
|
||||
std::vector<std::string, A> operator()(const Splitter& splitter) const {
|
||||
const std::vector<absl::string_view> v = splitter;
|
||||
return std::vector<std::string, A>(v.begin(), v.end());
|
||||
}
|
||||
};
|
||||
|
||||
// Partial specialization for containers of pairs (e.g., maps).
|
||||
//
|
||||
// The algorithm is to insert a new pair into the map for each even-numbered
|
||||
// item, with the even-numbered item as the key with a default-constructed
|
||||
// value. Each odd-numbered item will then be assigned to the last pair's
|
||||
// value.
|
||||
template <typename Container, typename First, typename Second>
|
||||
struct ConvertToContainer<Container, std::pair<const First, Second>, true> {
|
||||
using iterator = typename Container::iterator;
|
||||
|
||||
Container operator()(const Splitter& splitter) const {
|
||||
Container m;
|
||||
iterator it;
|
||||
bool insert = true;
|
||||
for (const absl::string_view sv : splitter) {
|
||||
if (insert) {
|
||||
it = InsertOrEmplace(&m, sv);
|
||||
} else {
|
||||
it->second = Second(sv);
|
||||
}
|
||||
insert = !insert;
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
// Inserts the key and an empty value into the map, returning an iterator to
|
||||
// the inserted item. We use emplace() if available, otherwise insert().
|
||||
template <typename M>
|
||||
static absl::enable_if_t<HasEmplace<M>::value, iterator> InsertOrEmplace(
|
||||
M* m, absl::string_view key) {
|
||||
// Use piecewise_construct to support old versions of gcc in which pair
|
||||
// constructor can't otherwise construct string from string_view.
|
||||
return ToIter(m->emplace(std::piecewise_construct, std::make_tuple(key),
|
||||
std::tuple<>()));
|
||||
}
|
||||
template <typename M>
|
||||
static absl::enable_if_t<!HasEmplace<M>::value, iterator> InsertOrEmplace(
|
||||
M* m, absl::string_view key) {
|
||||
return ToIter(m->insert(std::make_pair(First(key), Second(""))));
|
||||
}
|
||||
|
||||
static iterator ToIter(std::pair<iterator, bool> pair) {
|
||||
return pair.first;
|
||||
}
|
||||
static iterator ToIter(iterator iter) { return iter; }
|
||||
};
|
||||
|
||||
StringType text_;
|
||||
Delimiter delimiter_;
|
||||
Predicate predicate_;
|
||||
};
|
||||
|
||||
} // namespace strings_internal
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
|
||||
#endif // ABSL_STRINGS_INTERNAL_STR_SPLIT_INTERNAL_H_
|
||||
72
Pods/abseil/absl/strings/internal/string_constant.h
generated
Normal file
72
Pods/abseil/absl/strings/internal/string_constant.h
generated
Normal file
@@ -0,0 +1,72 @@
|
||||
// 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:
|
||||
static constexpr bool TryConstexprEval(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; }
|
||||
|
||||
// Check to be sure `view` points to constant data.
|
||||
// Otherwise, it can't be constant evaluated.
|
||||
static_assert(TryConstexprEval(value),
|
||||
"The input string_view must point to constant data.");
|
||||
};
|
||||
|
||||
#ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL
|
||||
template <typename T>
|
||||
constexpr absl::string_view StringConstant<T>::value;
|
||||
#endif
|
||||
|
||||
// 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_
|
||||
28
Pods/abseil/absl/strings/internal/stringify_sink.cc
generated
Normal file
28
Pods/abseil/absl/strings/internal/stringify_sink.cc
generated
Normal file
@@ -0,0 +1,28 @@
|
||||
// Copyright 2022 The Abseil Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "absl/strings/internal/stringify_sink.h"
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace strings_internal {
|
||||
|
||||
void StringifySink::Append(size_t count, char ch) { buffer_.append(count, ch); }
|
||||
|
||||
void StringifySink::Append(string_view v) {
|
||||
buffer_.append(v.data(), v.size());
|
||||
}
|
||||
|
||||
} // namespace strings_internal
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
57
Pods/abseil/absl/strings/internal/stringify_sink.h
generated
Normal file
57
Pods/abseil/absl/strings/internal/stringify_sink.h
generated
Normal file
@@ -0,0 +1,57 @@
|
||||
// Copyright 2022 The Abseil Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef ABSL_STRINGS_INTERNAL_STRINGIFY_SINK_H_
|
||||
#define ABSL_STRINGS_INTERNAL_STRINGIFY_SINK_H_
|
||||
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "absl/strings/string_view.h"
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
|
||||
namespace strings_internal {
|
||||
class StringifySink {
|
||||
public:
|
||||
void Append(size_t count, char ch);
|
||||
|
||||
void Append(string_view v);
|
||||
|
||||
// Support `absl::Format(&sink, format, args...)`.
|
||||
friend void AbslFormatFlush(StringifySink* sink, absl::string_view v) {
|
||||
sink->Append(v);
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename T>
|
||||
friend string_view ExtractStringification(StringifySink& sink, const T& v);
|
||||
|
||||
std::string buffer_;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
string_view ExtractStringification(StringifySink& sink, const T& v) {
|
||||
AbslStringify(sink, v);
|
||||
return sink.buffer_;
|
||||
}
|
||||
|
||||
} // namespace strings_internal
|
||||
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
|
||||
#endif // ABSL_STRINGS_INTERNAL_STRINGIFY_SINK_H_
|
||||
53
Pods/abseil/absl/strings/internal/utf8.cc
generated
Normal file
53
Pods/abseil/absl/strings/internal/utf8.cc
generated
Normal file
@@ -0,0 +1,53 @@
|
||||
// Copyright 2017 The Abseil Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// 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.
|
||||
|
||||
// UTF8 utilities, implemented to reduce dependencies.
|
||||
|
||||
#include "absl/strings/internal/utf8.h"
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace strings_internal {
|
||||
|
||||
size_t EncodeUTF8Char(char *buffer, char32_t utf8_char) {
|
||||
if (utf8_char <= 0x7F) {
|
||||
*buffer = static_cast<char>(utf8_char);
|
||||
return 1;
|
||||
} else if (utf8_char <= 0x7FF) {
|
||||
buffer[1] = static_cast<char>(0x80 | (utf8_char & 0x3F));
|
||||
utf8_char >>= 6;
|
||||
buffer[0] = static_cast<char>(0xC0 | utf8_char);
|
||||
return 2;
|
||||
} else if (utf8_char <= 0xFFFF) {
|
||||
buffer[2] = static_cast<char>(0x80 | (utf8_char & 0x3F));
|
||||
utf8_char >>= 6;
|
||||
buffer[1] = static_cast<char>(0x80 | (utf8_char & 0x3F));
|
||||
utf8_char >>= 6;
|
||||
buffer[0] = static_cast<char>(0xE0 | utf8_char);
|
||||
return 3;
|
||||
} else {
|
||||
buffer[3] = static_cast<char>(0x80 | (utf8_char & 0x3F));
|
||||
utf8_char >>= 6;
|
||||
buffer[2] = static_cast<char>(0x80 | (utf8_char & 0x3F));
|
||||
utf8_char >>= 6;
|
||||
buffer[1] = static_cast<char>(0x80 | (utf8_char & 0x3F));
|
||||
utf8_char >>= 6;
|
||||
buffer[0] = static_cast<char>(0xF0 | utf8_char);
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace strings_internal
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
50
Pods/abseil/absl/strings/internal/utf8.h
generated
Normal file
50
Pods/abseil/absl/strings/internal/utf8.h
generated
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright 2017 The Abseil Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// UTF8 utilities, implemented to reduce dependencies.
|
||||
|
||||
#ifndef ABSL_STRINGS_INTERNAL_UTF8_H_
|
||||
#define ABSL_STRINGS_INTERNAL_UTF8_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
#include "absl/base/config.h"
|
||||
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace strings_internal {
|
||||
|
||||
// For Unicode code points 0 through 0x10FFFF, EncodeUTF8Char writes
|
||||
// out the UTF-8 encoding into buffer, and returns the number of chars
|
||||
// it wrote.
|
||||
//
|
||||
// As described in https://tools.ietf.org/html/rfc3629#section-3 , the encodings
|
||||
// are:
|
||||
// 00 - 7F : 0xxxxxxx
|
||||
// 80 - 7FF : 110xxxxx 10xxxxxx
|
||||
// 800 - FFFF : 1110xxxx 10xxxxxx 10xxxxxx
|
||||
// 10000 - 10FFFF : 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
|
||||
//
|
||||
// Values greater than 0x10FFFF are not supported and may or may not write
|
||||
// characters into buffer, however never will more than kMaxEncodedUTF8Size
|
||||
// bytes be written, regardless of the value of utf8_char.
|
||||
enum { kMaxEncodedUTF8Size = 4 };
|
||||
size_t EncodeUTF8Char(char *buffer, char32_t utf8_char);
|
||||
|
||||
} // namespace strings_internal
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
|
||||
#endif // ABSL_STRINGS_INTERNAL_UTF8_H_
|
||||
Reference in New Issue
Block a user