Abseil Common Libraries (C++) (grcp 依赖)
https://abseil.io/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
324 lines
11 KiB
324 lines
11 KiB
#ifndef ABSL_STRINGS_INTERNAL_STR_FORMAT_CHECKER_H_ |
|
#define ABSL_STRINGS_INTERNAL_STR_FORMAT_CHECKER_H_ |
|
|
|
#include "absl/base/attributes.h" |
|
#include "absl/strings/internal/str_format/arg.h" |
|
#include "absl/strings/internal/str_format/extension.h" |
|
|
|
// Compile time check support for entry points. |
|
|
|
#ifndef ABSL_INTERNAL_ENABLE_FORMAT_CHECKER |
|
#if ABSL_HAVE_ATTRIBUTE(enable_if) && !defined(__native_client__) |
|
#define ABSL_INTERNAL_ENABLE_FORMAT_CHECKER 1 |
|
#endif // ABSL_HAVE_ATTRIBUTE(enable_if) && !defined(__native_client__) |
|
#endif // ABSL_INTERNAL_ENABLE_FORMAT_CHECKER |
|
|
|
namespace absl { |
|
namespace str_format_internal { |
|
|
|
constexpr bool AllOf() { return true; } |
|
|
|
template <typename... T> |
|
constexpr bool AllOf(bool b, T... t) { |
|
return b && AllOf(t...); |
|
} |
|
|
|
template <typename Arg> |
|
constexpr Conv ArgumentToConv() { |
|
return decltype(str_format_internal::FormatConvertImpl( |
|
std::declval<const Arg&>(), std::declval<const ConversionSpec&>(), |
|
std::declval<FormatSinkImpl*>()))::kConv; |
|
} |
|
|
|
#ifdef ABSL_INTERNAL_ENABLE_FORMAT_CHECKER |
|
|
|
constexpr bool ContainsChar(const char* chars, char c) { |
|
return *chars == c || (*chars && ContainsChar(chars + 1, c)); |
|
} |
|
|
|
// A constexpr compatible list of Convs. |
|
struct ConvList { |
|
const Conv* array; |
|
int count; |
|
|
|
// We do the bound check here to avoid having to do it on the callers. |
|
// Returning an empty Conv has the same effect as short circuiting because it |
|
// will never match any conversion. |
|
constexpr Conv operator[](int i) const { |
|
return i < count ? array[i] : Conv{}; |
|
} |
|
|
|
constexpr ConvList without_front() const { |
|
return count != 0 ? ConvList{array + 1, count - 1} : *this; |
|
} |
|
}; |
|
|
|
template <size_t count> |
|
struct ConvListT { |
|
// Make sure the array has size > 0. |
|
Conv list[count ? count : 1]; |
|
}; |
|
|
|
constexpr char GetChar(string_view str, size_t index) { |
|
return index < str.size() ? str[index] : char{}; |
|
} |
|
|
|
constexpr string_view ConsumeFront(string_view str, size_t len = 1) { |
|
return len <= str.size() ? string_view(str.data() + len, str.size() - len) |
|
: string_view(); |
|
} |
|
|
|
constexpr string_view ConsumeAnyOf(string_view format, const char* chars) { |
|
return ContainsChar(chars, GetChar(format, 0)) |
|
? ConsumeAnyOf(ConsumeFront(format), chars) |
|
: format; |
|
} |
|
|
|
constexpr bool IsDigit(char c) { return c >= '0' && c <= '9'; } |
|
|
|
// Helper class for the ParseDigits function. |
|
// It encapsulates the two return values we need there. |
|
struct Integer { |
|
string_view format; |
|
int value; |
|
|
|
// If the next character is a '$', consume it. |
|
// Otherwise, make `this` an invalid positional argument. |
|
constexpr Integer ConsumePositionalDollar() const { |
|
return GetChar(format, 0) == '$' ? Integer{ConsumeFront(format), value} |
|
: Integer{format, 0}; |
|
} |
|
}; |
|
|
|
constexpr Integer ParseDigits(string_view format, int value = 0) { |
|
return IsDigit(GetChar(format, 0)) |
|
? ParseDigits(ConsumeFront(format), |
|
10 * value + GetChar(format, 0) - '0') |
|
: Integer{format, value}; |
|
} |
|
|
|
// Parse digits for a positional argument. |
|
// The parsing also consumes the '$'. |
|
constexpr Integer ParsePositional(string_view format) { |
|
return ParseDigits(format).ConsumePositionalDollar(); |
|
} |
|
|
|
// Parses a single conversion specifier. |
|
// See ConvParser::Run() for post conditions. |
|
class ConvParser { |
|
constexpr ConvParser SetFormat(string_view format) const { |
|
return ConvParser(format, args_, error_, arg_position_, is_positional_); |
|
} |
|
|
|
constexpr ConvParser SetArgs(ConvList args) const { |
|
return ConvParser(format_, args, error_, arg_position_, is_positional_); |
|
} |
|
|
|
constexpr ConvParser SetError(bool error) const { |
|
return ConvParser(format_, args_, error_ || error, arg_position_, |
|
is_positional_); |
|
} |
|
|
|
constexpr ConvParser SetArgPosition(int arg_position) const { |
|
return ConvParser(format_, args_, error_, arg_position, is_positional_); |
|
} |
|
|
|
// Consumes the next arg and verifies that it matches `conv`. |
|
// `error_` is set if there is no next arg or if it doesn't match `conv`. |
|
constexpr ConvParser ConsumeNextArg(char conv) const { |
|
return SetArgs(args_.without_front()).SetError(!Contains(args_[0], conv)); |
|
} |
|
|
|
// Verify that positional argument `i.value` matches `conv`. |
|
// `error_` is set if `i.value` is not a valid argument or if it doesn't |
|
// match. |
|
constexpr ConvParser VerifyPositional(Integer i, char conv) const { |
|
return SetFormat(i.format).SetError(!Contains(args_[i.value - 1], conv)); |
|
} |
|
|
|
// Parse the position of the arg and store it in `arg_position_`. |
|
constexpr ConvParser ParseArgPosition(Integer arg) const { |
|
return SetFormat(arg.format).SetArgPosition(arg.value); |
|
} |
|
|
|
// Consume the flags. |
|
constexpr ConvParser ParseFlags() const { |
|
return SetFormat(ConsumeAnyOf(format_, "-+ #0")); |
|
} |
|
|
|
// Consume the width. |
|
// If it is '*', we verify that it matches `args_`. `error_` is set if it |
|
// doesn't match. |
|
constexpr ConvParser ParseWidth() const { |
|
return IsDigit(GetChar(format_, 0)) |
|
? SetFormat(ParseDigits(format_).format) |
|
: GetChar(format_, 0) == '*' |
|
? is_positional_ |
|
? VerifyPositional( |
|
ParsePositional(ConsumeFront(format_)), '*') |
|
: SetFormat(ConsumeFront(format_)) |
|
.ConsumeNextArg('*') |
|
: *this; |
|
} |
|
|
|
// Consume the precision. |
|
// If it is '*', we verify that it matches `args_`. `error_` is set if it |
|
// doesn't match. |
|
constexpr ConvParser ParsePrecision() const { |
|
return GetChar(format_, 0) != '.' |
|
? *this |
|
: GetChar(format_, 1) == '*' |
|
? is_positional_ |
|
? VerifyPositional( |
|
ParsePositional(ConsumeFront(format_, 2)), '*') |
|
: SetFormat(ConsumeFront(format_, 2)) |
|
.ConsumeNextArg('*') |
|
: SetFormat(ParseDigits(ConsumeFront(format_)).format); |
|
} |
|
|
|
// Consume the length characters. |
|
constexpr ConvParser ParseLength() const { |
|
return SetFormat(ConsumeAnyOf(format_, "lLhjztq")); |
|
} |
|
|
|
// Consume the conversion character and verify that it matches `args_`. |
|
// `error_` is set if it doesn't match. |
|
constexpr ConvParser ParseConversion() const { |
|
return is_positional_ |
|
? VerifyPositional({ConsumeFront(format_), arg_position_}, |
|
GetChar(format_, 0)) |
|
: ConsumeNextArg(GetChar(format_, 0)) |
|
.SetFormat(ConsumeFront(format_)); |
|
} |
|
|
|
constexpr ConvParser(string_view format, ConvList args, bool error, |
|
int arg_position, bool is_positional) |
|
: format_(format), |
|
args_(args), |
|
error_(error), |
|
arg_position_(arg_position), |
|
is_positional_(is_positional) {} |
|
|
|
public: |
|
constexpr ConvParser(string_view format, ConvList args, bool is_positional) |
|
: format_(format), |
|
args_(args), |
|
error_(false), |
|
arg_position_(0), |
|
is_positional_(is_positional) {} |
|
|
|
// Consume the whole conversion specifier. |
|
// `format()` will be set to the character after the conversion character. |
|
// `error()` will be set if any of the arguments do not match. |
|
constexpr ConvParser Run() const { |
|
return (is_positional_ ? ParseArgPosition(ParsePositional(format_)) : *this) |
|
.ParseFlags() |
|
.ParseWidth() |
|
.ParsePrecision() |
|
.ParseLength() |
|
.ParseConversion(); |
|
} |
|
|
|
constexpr string_view format() const { return format_; } |
|
constexpr ConvList args() const { return args_; } |
|
constexpr bool error() const { return error_; } |
|
constexpr bool is_positional() const { return is_positional_; } |
|
|
|
private: |
|
string_view format_; |
|
// Current list of arguments. If we are not in positional mode we will consume |
|
// from the front. |
|
ConvList args_; |
|
bool error_; |
|
// Holds the argument position of the conversion character, if we are in |
|
// positional mode. Otherwise, it is unspecified. |
|
int arg_position_; |
|
// Whether we are in positional mode. |
|
// It changes the behavior of '*' and where to find the converted argument. |
|
bool is_positional_; |
|
}; |
|
|
|
// Parses a whole format expression. |
|
// See FormatParser::Run(). |
|
class FormatParser { |
|
static constexpr bool FoundPercent(string_view format) { |
|
return format.empty() || |
|
(GetChar(format, 0) == '%' && GetChar(format, 1) != '%'); |
|
} |
|
|
|
// We use an inner function to increase the recursion limit. |
|
// The inner function consumes up to `limit` characters on every run. |
|
// This increases the limit from 512 to ~512*limit. |
|
static constexpr string_view ConsumeNonPercentInner(string_view format, |
|
int limit = 20) { |
|
return FoundPercent(format) || !limit |
|
? format |
|
: ConsumeNonPercentInner( |
|
ConsumeFront(format, GetChar(format, 0) == '%' && |
|
GetChar(format, 1) == '%' |
|
? 2 |
|
: 1), |
|
limit - 1); |
|
} |
|
|
|
// Consume characters until the next conversion spec %. |
|
// It skips %%. |
|
static constexpr string_view ConsumeNonPercent(string_view format) { |
|
return FoundPercent(format) |
|
? format |
|
: ConsumeNonPercent(ConsumeNonPercentInner(format)); |
|
} |
|
|
|
static constexpr bool IsPositional(string_view format) { |
|
return IsDigit(GetChar(format, 0)) ? IsPositional(ConsumeFront(format)) |
|
: GetChar(format, 0) == '$'; |
|
} |
|
|
|
constexpr bool RunImpl(bool is_positional) const { |
|
// In non-positional mode we require all arguments to be consumed. |
|
// In positional mode just reaching the end of the format without errors is |
|
// enough. |
|
return (format_.empty() && (is_positional || args_.count == 0)) || |
|
(!format_.empty() && |
|
ValidateArg( |
|
ConvParser(ConsumeFront(format_), args_, is_positional).Run())); |
|
} |
|
|
|
constexpr bool ValidateArg(ConvParser conv) const { |
|
return !conv.error() && FormatParser(conv.format(), conv.args()) |
|
.RunImpl(conv.is_positional()); |
|
} |
|
|
|
public: |
|
constexpr FormatParser(string_view format, ConvList args) |
|
: format_(ConsumeNonPercent(format)), args_(args) {} |
|
|
|
// Runs the parser for `format` and `args`. |
|
// It verifies that the format is valid and that all conversion specifiers |
|
// match the arguments passed. |
|
// In non-positional mode it also verfies that all arguments are consumed. |
|
constexpr bool Run() const { |
|
return RunImpl(!format_.empty() && IsPositional(ConsumeFront(format_))); |
|
} |
|
|
|
private: |
|
string_view format_; |
|
// Current list of arguments. |
|
// If we are not in positional mode we will consume from the front and will |
|
// have to be empty in the end. |
|
ConvList args_; |
|
}; |
|
|
|
template <Conv... C> |
|
constexpr bool ValidFormatImpl(string_view format) { |
|
return FormatParser(format, |
|
{ConvListT<sizeof...(C)>{{C...}}.list, sizeof...(C)}) |
|
.Run(); |
|
} |
|
|
|
#endif // ABSL_INTERNAL_ENABLE_FORMAT_CHECKER |
|
|
|
} // namespace str_format_internal |
|
} // namespace absl |
|
|
|
#endif // ABSL_STRINGS_INTERNAL_STR_FORMAT_CHECKER_H_
|
|
|