bd317cae3b
-- 0b3113ff8d252be11c35d8a77cdb23cc03e993d2 by Benjamin Barenblat <bbaren@google.com>: Correct Apache license headers These files have always been licensed under the Apache license (per the repository LICENSE), but they lacked headers to locally indicate that fact. Add the appropriate headers. PiperOrigin-RevId: 310223650 -- 4f92b0819781279c02b48bcfce4c10d547e35a49 by Greg Falcon <gfalcon@google.com>: Internal change PiperOrigin-RevId: 310223203 -- 378cab69ce3290d20b48d839daa988c3e63ed031 by Matthew Brown <matthewbr@google.com>: Internal Change PiperOrigin-RevId: 310180198 -- 93a1851be0f3726f7435cef56afaa1b8664f40e7 by Gennadiy Rozental <rogeeff@google.com>: Internal change PiperOrigin-RevId: 310003669 -- fbee3221cb1f8717fdf34e1cc0ea248a7719227a by Gennadiy Rozental <rogeeff@google.com>: Make 4 argument version of ParseFrom interface private in CommandLineFlag. Instroduce 2 argument version of this interface. PiperOrigin-RevId: 309995363 GitOrigin-RevId: 0b3113ff8d252be11c35d8a77cdb23cc03e993d2 Change-Id: Ibfdf948d3d081f5630ce7ebff992ab85c75cecba
336 lines
11 KiB
C++
336 lines
11 KiB
C++
#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 {
|
|
|
|
using CC = FormatConversionCharInternal;
|
|
using LM = LengthMod;
|
|
|
|
ABSL_CONST_INIT const ConvTag kTags[256] = {
|
|
{}, {}, {}, {}, {}, {}, {}, {}, // 00-07
|
|
{}, {}, {}, {}, {}, {}, {}, {}, // 08-0f
|
|
{}, {}, {}, {}, {}, {}, {}, {}, // 10-17
|
|
{}, {}, {}, {}, {}, {}, {}, {}, // 18-1f
|
|
{}, {}, {}, {}, {}, {}, {}, {}, // 20-27
|
|
{}, {}, {}, {}, {}, {}, {}, {}, // 28-2f
|
|
{}, {}, {}, {}, {}, {}, {}, {}, // 30-37
|
|
{}, {}, {}, {}, {}, {}, {}, {}, // 38-3f
|
|
{}, 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, {}, {}, // pqrstuvw
|
|
CC::x, {}, LM::z, {}, {}, {}, {}, {}, // xyz{|}!
|
|
{}, {}, {}, {}, {}, {}, {}, {}, // 80-87
|
|
{}, {}, {}, {}, {}, {}, {}, {}, // 88-8f
|
|
{}, {}, {}, {}, {}, {}, {}, {}, // 90-97
|
|
{}, {}, {}, {}, {}, {}, {}, {}, // 98-9f
|
|
{}, {}, {}, {}, {}, {}, {}, {}, // a0-a7
|
|
{}, {}, {}, {}, {}, {}, {}, {}, // a8-af
|
|
{}, {}, {}, {}, {}, {}, {}, {}, // b0-b7
|
|
{}, {}, {}, {}, {}, {}, {}, {}, // b8-bf
|
|
{}, {}, {}, {}, {}, {}, {}, {}, // c0-c7
|
|
{}, {}, {}, {}, {}, {}, {}, {}, // c8-cf
|
|
{}, {}, {}, {}, {}, {}, {}, {}, // d0-d7
|
|
{}, {}, {}, {}, {}, {}, {}, {}, // d8-df
|
|
{}, {}, {}, {}, {}, {}, {}, {}, // e0-e7
|
|
{}, {}, {}, {}, {}, {}, {}, {}, // e8-ef
|
|
{}, {}, {}, {}, {}, {}, {}, {}, // f0-f7
|
|
{}, {}, {}, {}, {}, {}, {}, {}, // f8-ff
|
|
};
|
|
|
|
namespace {
|
|
|
|
bool CheckFastPathSetting(const UnboundConversion& conv) {
|
|
bool should_be_basic = !conv.flags.left && //
|
|
!conv.flags.show_pos && //
|
|
!conv.flags.sign_col && //
|
|
!conv.flags.alt && //
|
|
!conv.flags.zero && //
|
|
(conv.width.value() == -1) &&
|
|
(conv.precision.value() == -1);
|
|
if (should_be_basic != conv.flags.basic) {
|
|
fprintf(stderr,
|
|
"basic=%d left=%d show_pos=%d sign_col=%d alt=%d zero=%d "
|
|
"width=%d precision=%d\n",
|
|
conv.flags.basic, conv.flags.left, conv.flags.show_pos,
|
|
conv.flags.sign_col, conv.flags.alt, conv.flags.zero,
|
|
conv.width.value(), conv.precision.value());
|
|
}
|
|
return should_be_basic == conv.flags.basic;
|
|
}
|
|
|
|
template <bool is_positional>
|
|
const char *ConsumeConversion(const char *pos, const char *const end,
|
|
UnboundConversion *conv, int *next_arg) {
|
|
const char* const original_pos = pos;
|
|
char c;
|
|
// Read the next char into `c` and update `pos`. Returns false if there are
|
|
// no more chars to read.
|
|
#define ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR() \
|
|
do { \
|
|
if (ABSL_PREDICT_FALSE(pos == end)) return nullptr; \
|
|
c = *pos++; \
|
|
} while (0)
|
|
|
|
const auto parse_digits = [&] {
|
|
int digits = c - '0';
|
|
// We do not want to overflow `digits` so we consume at most digits10
|
|
// digits. If there are more digits the parsing will fail later on when the
|
|
// digit doesn't match the expected characters.
|
|
int num_digits = std::numeric_limits<int>::digits10;
|
|
for (;;) {
|
|
if (ABSL_PREDICT_FALSE(pos == end)) break;
|
|
c = *pos++;
|
|
if (!std::isdigit(c)) break;
|
|
--num_digits;
|
|
if (ABSL_PREDICT_FALSE(!num_digits)) break;
|
|
digits = 10 * digits + c - '0';
|
|
}
|
|
return digits;
|
|
};
|
|
|
|
if (is_positional) {
|
|
ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR();
|
|
if (ABSL_PREDICT_FALSE(c < '1' || c > '9')) return nullptr;
|
|
conv->arg_position = parse_digits();
|
|
assert(conv->arg_position > 0);
|
|
if (ABSL_PREDICT_FALSE(c != '$')) return nullptr;
|
|
}
|
|
|
|
ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR();
|
|
|
|
// We should start with the basic flag on.
|
|
assert(conv->flags.basic);
|
|
|
|
// Any non alpha character makes this conversion not basic.
|
|
// This includes flags (-+ #0), width (1-9, *) or precision (.).
|
|
// All conversion characters and length modifiers are alpha characters.
|
|
if (c < 'A') {
|
|
conv->flags.basic = false;
|
|
|
|
for (; c <= '0';) {
|
|
// FIXME: We might be able to speed this up reusing the lookup table from
|
|
// above. It might require changing Flags to be a plain integer where we
|
|
// can |= a value.
|
|
switch (c) {
|
|
case '-':
|
|
conv->flags.left = true;
|
|
break;
|
|
case '+':
|
|
conv->flags.show_pos = true;
|
|
break;
|
|
case ' ':
|
|
conv->flags.sign_col = true;
|
|
break;
|
|
case '#':
|
|
conv->flags.alt = true;
|
|
break;
|
|
case '0':
|
|
conv->flags.zero = true;
|
|
break;
|
|
default:
|
|
goto flags_done;
|
|
}
|
|
ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR();
|
|
}
|
|
flags_done:
|
|
|
|
if (c <= '9') {
|
|
if (c >= '0') {
|
|
int maybe_width = parse_digits();
|
|
if (!is_positional && c == '$') {
|
|
if (ABSL_PREDICT_FALSE(*next_arg != 0)) return nullptr;
|
|
// Positional conversion.
|
|
*next_arg = -1;
|
|
conv->flags = Flags();
|
|
conv->flags.basic = true;
|
|
return ConsumeConversion<true>(original_pos, end, conv, next_arg);
|
|
}
|
|
conv->width.set_value(maybe_width);
|
|
} else if (c == '*') {
|
|
ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR();
|
|
if (is_positional) {
|
|
if (ABSL_PREDICT_FALSE(c < '1' || c > '9')) return nullptr;
|
|
conv->width.set_from_arg(parse_digits());
|
|
if (ABSL_PREDICT_FALSE(c != '$')) return nullptr;
|
|
ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR();
|
|
} else {
|
|
conv->width.set_from_arg(++*next_arg);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (c == '.') {
|
|
ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR();
|
|
if (std::isdigit(c)) {
|
|
conv->precision.set_value(parse_digits());
|
|
} else if (c == '*') {
|
|
ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR();
|
|
if (is_positional) {
|
|
if (ABSL_PREDICT_FALSE(c < '1' || c > '9')) return nullptr;
|
|
conv->precision.set_from_arg(parse_digits());
|
|
if (c != '$') return nullptr;
|
|
ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR();
|
|
} else {
|
|
conv->precision.set_from_arg(++*next_arg);
|
|
}
|
|
} else {
|
|
conv->precision.set_value(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
auto tag = GetTagForChar(c);
|
|
|
|
if (ABSL_PREDICT_FALSE(!tag.is_conv())) {
|
|
if (ABSL_PREDICT_FALSE(!tag.is_length())) return nullptr;
|
|
|
|
// It is a length modifier.
|
|
using str_format_internal::LengthMod;
|
|
LengthMod length_mod = tag.as_length();
|
|
ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR();
|
|
if (c == 'h' && length_mod == LengthMod::h) {
|
|
conv->length_mod = LengthMod::hh;
|
|
ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR();
|
|
} else if (c == 'l' && length_mod == LengthMod::l) {
|
|
conv->length_mod = LengthMod::ll;
|
|
ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR();
|
|
} else {
|
|
conv->length_mod = length_mod;
|
|
}
|
|
tag = GetTagForChar(c);
|
|
if (ABSL_PREDICT_FALSE(!tag.is_conv())) return nullptr;
|
|
}
|
|
|
|
assert(CheckFastPathSetting(*conv));
|
|
(void)(&CheckFastPathSetting);
|
|
|
|
conv->conv = tag.as_conv();
|
|
if (!is_positional) conv->arg_position = ++*next_arg;
|
|
return pos;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
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 "";
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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
|