summaryrefslogtreecommitdiff
path: root/absl/strings/internal/str_format/checker.h
blob: 4fd19d13aeb0f59dacc1b326df4dcb23c0fa1229 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
// 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 "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
// 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 {

constexpr bool AllOf() { return true; }

template <typename... T>
constexpr bool AllOf(bool b, T... t) {
  return b && AllOf(t...);
}

#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 FormatConversionCharSet* array;
  int count;

  // We do the bound check here to avoid having to do it on the callers.
  // Returning an empty FormatConversionCharSet has the same effect as
  // short circuiting because it will never match any conversion.
  constexpr FormatConversionCharSet operator[](int i) const {
    return i < count ? array[i] : FormatConversionCharSet{};
  }

  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.
  FormatConversionCharSet 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 <FormatConversionCharSet... 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
ABSL_NAMESPACE_END
}  // namespace absl

#endif  // ABSL_STRINGS_INTERNAL_STR_FORMAT_CHECKER_H_