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