summaryrefslogtreecommitdiff
path: root/src/stdio/printf_core/parser.h
blob: eda978a83ea8af82a22ae20935f0f5e0fa238379 (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
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
//===-- Format string parser for printf -------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_LIBC_SRC_STDIO_PRINTF_CORE_PARSER_H
#define LLVM_LIBC_SRC_STDIO_PRINTF_CORE_PARSER_H

#include "include/llvm-libc-macros/stdfix-macros.h"
#include "src/__support/CPP/algorithm.h" // max
#include "src/__support/CPP/optional.h"
#include "src/__support/CPP/type_traits.h"
#include "src/__support/str_to_integer.h"
#include "src/stdio/printf_core/core_structs.h"
#include "src/stdio/printf_core/printf_config.h"

#include <stddef.h>

#ifdef LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT
#include "src/__support/fixed_point/fx_rep.h"
#endif // LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT

namespace LIBC_NAMESPACE {
namespace printf_core {

template <typename T> struct int_type_of {
  using type = T;
};
template <> struct int_type_of<double> {
  using type = fputil::FPBits<double>::StorageType;
};
template <> struct int_type_of<long double> {
  using type = fputil::FPBits<long double>::StorageType;
};

#ifdef LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT
template <typename T>
struct int_type_of<cpp::enable_if<cpp::is_fixed_point_v<T>, T>> {
  using type = typename fixed_point::FXRep<T>::StorageType;
};
#endif // LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT

template <typename T> using int_type_of_v = typename int_type_of<T>::type;

#ifndef LIBC_COPT_PRINTF_DISABLE_INDEX_MODE
#define WRITE_ARG_VAL_SIMPLEST(dst, arg_type, index)                           \
  {                                                                            \
    auto temp = get_arg_value<arg_type>(index);                                \
    if (!temp.has_value()) {                                                   \
      section.has_conv = false;                                                \
    } else {                                                                   \
      dst = cpp::bit_cast<int_type_of_v<arg_type>>(temp.value());              \
    }                                                                          \
  }
#else
#define WRITE_ARG_VAL_SIMPLEST(dst, arg_type, _)                               \
  dst = cpp::bit_cast<int_type_of_v<arg_type>>(get_next_arg_value<arg_type>())
#endif // LIBC_COPT_PRINTF_DISABLE_INDEX_MODE

template <typename ArgProvider> class Parser {
  const char *__restrict str;

  size_t cur_pos = 0;
  ArgProvider args_cur;

#ifndef LIBC_COPT_PRINTF_DISABLE_INDEX_MODE
  // args_start stores the start of the va_args, which is allows getting the
  // value of arguments that have already been passed. args_index is tracked so
  // that we know which argument args_cur is on.
  ArgProvider args_start;
  size_t args_index = 1;

  // Defined in printf_config.h
  static constexpr size_t DESC_ARR_LEN = LIBC_COPT_PRINTF_INDEX_ARR_LEN;

  // desc_arr stores the sizes of the variables in the ArgProvider. This is used
  // in index mode to reduce repeated string parsing. The sizes are stored as
  // TypeDesc objects, which store the size as well as minimal type information.
  // This is necessary because some systems separate the floating point and
  // integer values in va_args.
  TypeDesc desc_arr[DESC_ARR_LEN] = {type_desc_from_type<void>()};

  // TODO: Look into object stores for optimization.

#endif // LIBC_COPT_PRINTF_DISABLE_INDEX_MODE

public:
#ifndef LIBC_COPT_PRINTF_DISABLE_INDEX_MODE
  LIBC_INLINE Parser(const char *__restrict new_str, ArgProvider &args)
      : str(new_str), args_cur(args), args_start(args) {}
#else
  LIBC_INLINE Parser(const char *__restrict new_str, ArgProvider &args)
      : str(new_str), args_cur(args) {}
#endif // LIBC_COPT_PRINTF_DISABLE_INDEX_MODE

  // get_next_section will parse the format string until it has a fully
  // specified format section. This can either be a raw format section with no
  // conversion, or a format section with a conversion that has all of its
  // variables stored in the format section.
  LIBC_INLINE FormatSection get_next_section() {
    FormatSection section;
    size_t starting_pos = cur_pos;
    if (str[cur_pos] == '%') {
      // format section
      section.has_conv = true;

      ++cur_pos;
      [[maybe_unused]] size_t conv_index = 0;

#ifndef LIBC_COPT_PRINTF_DISABLE_INDEX_MODE
      conv_index = parse_index(&cur_pos);
#endif // LIBC_COPT_PRINTF_DISABLE_INDEX_MODE

      section.flags = parse_flags(&cur_pos);

      // handle width
      section.min_width = 0;
      if (str[cur_pos] == '*') {
        ++cur_pos;

        WRITE_ARG_VAL_SIMPLEST(section.min_width, int, parse_index(&cur_pos));
      } else if (internal::isdigit(str[cur_pos])) {
        auto result = internal::strtointeger<int>(str + cur_pos, 10);
        section.min_width = result.value;
        cur_pos = cur_pos + result.parsed_len;
      }
      if (section.min_width < 0) {
        section.min_width = -section.min_width;
        section.flags = static_cast<FormatFlags>(section.flags |
                                                 FormatFlags::LEFT_JUSTIFIED);
      }

      // handle precision
      section.precision = -1; // negative precisions are ignored.
      if (str[cur_pos] == '.') {
        ++cur_pos;
        section.precision = 0; // if there's a . but no specified precision, the
                               // precision is implicitly 0.
        if (str[cur_pos] == '*') {
          ++cur_pos;

          WRITE_ARG_VAL_SIMPLEST(section.precision, int, parse_index(&cur_pos));

        } else if (internal::isdigit(str[cur_pos])) {
          auto result = internal::strtointeger<int>(str + cur_pos, 10);
          section.precision = result.value;
          cur_pos = cur_pos + result.parsed_len;
        }
      }

      auto [lm, bw] = parse_length_modifier(&cur_pos);
      section.length_modifier = lm;
      section.conv_name = str[cur_pos];
      section.bit_width = bw;
      switch (str[cur_pos]) {
      case ('%'):
        // Regardless of options, a % conversion is always safe. The standard
        // says that "The complete conversion specification shall be %%" but it
        // also says that "If a conversion specification is invalid, the
        // behavior is undefined." Based on that we define that any conversion
        // specification ending in '%' shall display as '%' regardless of any
        // valid or invalid options.
        section.has_conv = true;
        break;
      case ('c'):
        WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, int, conv_index);
        break;
      case ('d'):
      case ('i'):
      case ('o'):
      case ('x'):
      case ('X'):
      case ('u'):
      case ('b'):
      case ('B'):
        switch (lm) {
        case (LengthModifier::hh):
        case (LengthModifier::h):
        case (LengthModifier::none):
          WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, int, conv_index);
          break;
        case (LengthModifier::l):
          WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, long, conv_index);
          break;
        case (LengthModifier::ll):
        case (LengthModifier::L): // This isn't in the standard, but is in other
                                  // libc implementations.

          WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, long long, conv_index);
          break;
        case (LengthModifier::j):

          WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, intmax_t, conv_index);
          break;
        case (LengthModifier::z):

          WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, size_t, conv_index);
          break;
        case (LengthModifier::t):

          WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, ptrdiff_t, conv_index);
          break;

        case (LengthModifier::w):
        case (LengthModifier::wf):
          if (bw == 0) {
            section.has_conv = false;
          } else if (bw <= INT_WIDTH) {
            WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, int, conv_index);
          } else if (bw <= LONG_WIDTH) {
            WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, long, conv_index);
          } else if (bw <= LLONG_WIDTH) {
            WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, long long, conv_index);
          } else {
            WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, intmax_t, conv_index);
          }
          break;
        }
        break;
#ifndef LIBC_COPT_PRINTF_DISABLE_FLOAT
      case ('f'):
      case ('F'):
      case ('e'):
      case ('E'):
      case ('a'):
      case ('A'):
      case ('g'):
      case ('G'):
        if (lm != LengthModifier::L) {
          WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, double, conv_index);
        } else {
          WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, long double, conv_index);
        }
        break;
#endif // LIBC_COPT_PRINTF_DISABLE_FLOAT
#ifdef LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT
      // Capitalization represents sign, but we only need to get the right
      // bitwidth here so we ignore that.
      case ('r'):
      case ('R'):
        // all fract sizes we support are less than 32 bits, and currently doing
        // va_args with fixed point types just doesn't work.
        // TODO: Move to fixed point types once va_args supports it.
        WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, uint32_t, conv_index);
        break;
      case ('k'):
      case ('K'):
        if (lm == LengthModifier::l) {
          WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, uint64_t, conv_index);
        } else {
          WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, uint32_t, conv_index);
        }
        break;
#endif // LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT
#ifndef LIBC_COPT_PRINTF_DISABLE_WRITE_INT
      case ('n'):
#endif // LIBC_COPT_PRINTF_DISABLE_WRITE_INT
      case ('p'):
        WRITE_ARG_VAL_SIMPLEST(section.conv_val_ptr, void *, conv_index);
        break;
      case ('s'):
        WRITE_ARG_VAL_SIMPLEST(section.conv_val_ptr, char *, conv_index);
        break;
      default:
        // if the conversion is undefined, change this to a raw section.
        section.has_conv = false;
        break;
      }
      // If the end of the format section is on the '\0'. This means we need to
      // not advance the cur_pos.
      if (str[cur_pos] != '\0')
        ++cur_pos;

    } else {
      // raw section
      section.has_conv = false;
      while (str[cur_pos] != '%' && str[cur_pos] != '\0')
        ++cur_pos;
    }
    section.raw_string = {str + starting_pos, cur_pos - starting_pos};
    return section;
  }

private:
  // parse_flags parses the flags inside a format string. It assumes that
  // str[*local_pos] is inside a format specifier, and parses any flags it
  // finds. It returns a FormatFlags object containing the set of found flags
  // arithmetically or'd together. local_pos will be moved past any flags found.
  LIBC_INLINE FormatFlags parse_flags(size_t *local_pos) {
    bool found_flag = true;
    FormatFlags flags = FormatFlags(0);
    while (found_flag) {
      switch (str[*local_pos]) {
      case '-':
        flags = static_cast<FormatFlags>(flags | FormatFlags::LEFT_JUSTIFIED);
        break;
      case '+':
        flags = static_cast<FormatFlags>(flags | FormatFlags::FORCE_SIGN);
        break;
      case ' ':
        flags = static_cast<FormatFlags>(flags | FormatFlags::SPACE_PREFIX);
        break;
      case '#':
        flags = static_cast<FormatFlags>(flags | FormatFlags::ALTERNATE_FORM);
        break;
      case '0':
        flags = static_cast<FormatFlags>(flags | FormatFlags::LEADING_ZEROES);
        break;
      default:
        found_flag = false;
      }
      if (found_flag)
        ++*local_pos;
    }
    return flags;
  }

  // parse_length_modifier parses the length modifier inside a format string. It
  // assumes that str[*local_pos] is inside a format specifier. It returns a
  // LengthModifier with the length modifier it found. It will advance local_pos
  // after the format specifier if one is found.
  LIBC_INLINE LengthSpec parse_length_modifier(size_t *local_pos) {
    switch (str[*local_pos]) {
    case ('l'):
      if (str[*local_pos + 1] == 'l') {
        *local_pos += 2;
        return {LengthModifier::ll, 0};
      } else {
        ++*local_pos;
        return {LengthModifier::l, 0};
      }
    case ('w'): {
      LengthModifier lm;
      if (str[*local_pos + 1] == 'f') {
        *local_pos += 2;
        lm = LengthModifier::wf;
      } else {
        ++*local_pos;
        lm = LengthModifier::w;
      }
      if (internal::isdigit(str[*local_pos])) {
        const auto result = internal::strtointeger<int>(str + *local_pos, 10);
        *local_pos += result.parsed_len;
        return {lm, static_cast<size_t>(cpp::max(0, result.value))};
      }
      return {lm, 0};
    }
    case ('h'):
      if (str[*local_pos + 1] == 'h') {
        *local_pos += 2;
        return {LengthModifier::hh, 0};
      } else {
        ++*local_pos;
        return {LengthModifier::h, 0};
      }
    case ('L'):
      ++*local_pos;
      return {LengthModifier::L, 0};
    case ('j'):
      ++*local_pos;
      return {LengthModifier::j, 0};
    case ('z'):
      ++*local_pos;
      return {LengthModifier::z, 0};
    case ('t'):
      ++*local_pos;
      return {LengthModifier::t, 0};
    default:
      return {LengthModifier::none, 0};
    }
  }

  // get_next_arg_value gets the next value from the arg list as type T.
  template <class T> LIBC_INLINE T get_next_arg_value() {
    return args_cur.template next_var<T>();
  }

  //----------------------------------------------------
  // INDEX MODE ONLY FUNCTIONS AFTER HERE:
  //----------------------------------------------------

#ifndef LIBC_COPT_PRINTF_DISABLE_INDEX_MODE

  // parse_index parses the index of a value inside a format string. It
  // assumes that str[*local_pos] points to character after a '%' or '*', and
  // returns 0 if there is no closing $, or if it finds no number. If it finds a
  // number, it will move local_pos past the end of the $, else it will not move
  // local_pos.
  LIBC_INLINE size_t parse_index(size_t *local_pos) {
    if (internal::isdigit(str[*local_pos])) {
      auto result = internal::strtointeger<int>(str + *local_pos, 10);
      size_t index = result.value;
      if (str[*local_pos + result.parsed_len] != '$')
        return 0;
      *local_pos = 1 + result.parsed_len + *local_pos;
      return index;
    }
    return 0;
  }

  LIBC_INLINE void set_type_desc(size_t index, TypeDesc value) {
    if (index != 0 && index <= DESC_ARR_LEN)
      desc_arr[index - 1] = value;
  }

  // get_arg_value gets the value from the arg list at index (starting at 1).
  // This may require parsing the format string. An index of 0 is interpreted as
  // the next value. If the format string is not valid, it may have gaps in its
  // indexes. Requesting the value for any index after a gap will fail, since
  // the arg list must be read in order and with the correct types.
  template <class T> LIBC_INLINE cpp::optional<T> get_arg_value(size_t index) {
    if (!(index == 0 || index == args_index)) {
      bool success = args_to_index(index);
      if (!success) {
        // If we can't get to this index, then the value of the arg can't be
        // found.
        return cpp::optional<T>();
      }
    }

    set_type_desc(index, type_desc_from_type<T>());

    ++args_index;
    return get_next_arg_value<T>();
  }

  // the ArgProvider can only return the next item in the list. This function is
  // used in index mode when the item that needs to be read is not the next one.
  // It moves cur_args to the index requested so the appropriate value may
  // be read. This may involve parsing the format string, and is in the worst
  // case an O(n^2) operation.
  LIBC_INLINE bool args_to_index(size_t index) {
    if (args_index > index) {
      args_index = 1;
      args_cur = args_start;
    }

    while (args_index < index) {
      TypeDesc cur_type_desc = type_desc_from_type<void>();
      if (args_index <= DESC_ARR_LEN)
        cur_type_desc = desc_arr[args_index - 1];

      if (cur_type_desc == type_desc_from_type<void>())
        cur_type_desc = get_type_desc(args_index);

      // A type of void represents the type being unknown. If the type for the
      // requested index isn't in the desc_arr and isn't found by parsing the
      // string, then then advancing to the requested index is impossible. In
      // that case the function returns false.
      if (cur_type_desc == type_desc_from_type<void>())
        return false;

      if (cur_type_desc == type_desc_from_type<uint32_t>())
        args_cur.template next_var<uint32_t>();
      else if (cur_type_desc == type_desc_from_type<uint64_t>())
        args_cur.template next_var<uint64_t>();
#ifndef LIBC_COPT_PRINTF_DISABLE_FLOAT
      // Floating point numbers are stored separately from the other arguments.
      else if (cur_type_desc == type_desc_from_type<double>())
        args_cur.template next_var<double>();
      else if (cur_type_desc == type_desc_from_type<long double>())
        args_cur.template next_var<long double>();
#endif // LIBC_COPT_PRINTF_DISABLE_FLOAT
#ifdef LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT
      // Floating point numbers may be stored separately from the other
      // arguments.
      else if (cur_type_desc == type_desc_from_type<short fract>())
        args_cur.template next_var<short fract>();
      else if (cur_type_desc == type_desc_from_type<fract>())
        args_cur.template next_var<fract>();
      else if (cur_type_desc == type_desc_from_type<long fract>())
        args_cur.template next_var<long fract>();
      else if (cur_type_desc == type_desc_from_type<short accum>())
        args_cur.template next_var<short accum>();
      else if (cur_type_desc == type_desc_from_type<accum>())
        args_cur.template next_var<accum>();
      else if (cur_type_desc == type_desc_from_type<long accum>())
        args_cur.template next_var<long accum>();
#endif // LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT
      // pointers may be stored separately from normal values.
      else if (cur_type_desc == type_desc_from_type<void *>())
        args_cur.template next_var<void *>();
      else
        args_cur.template next_var<uint32_t>();

      ++args_index;
    }
    return true;
  }

  // get_type_desc assumes that this format string uses index mode. It iterates
  // through the format string until it finds a format specifier that defines
  // the type of index, and returns a TypeDesc describing that type. It does not
  // modify cur_pos.
  LIBC_INLINE TypeDesc get_type_desc(size_t index) {
    // index mode is assumed, and the indicies start at 1, so an index
    // of 0 is invalid.
    size_t local_pos = 0;

    while (str[local_pos]) {
      if (str[local_pos] == '%') {
        ++local_pos;

        size_t conv_index = parse_index(&local_pos);

        // the flags aren't relevant for this situation, but I need to skip past
        // them so they're parsed but the result is discarded.
        parse_flags(&local_pos);

        // handle width
        if (str[local_pos] == '*') {
          ++local_pos;

          size_t width_index = parse_index(&local_pos);
          set_type_desc(width_index, type_desc_from_type<int>());
          if (width_index == index)
            return type_desc_from_type<int>();

        } else if (internal::isdigit(str[local_pos])) {
          while (internal::isdigit(str[local_pos]))
            ++local_pos;
        }

        // handle precision
        if (str[local_pos] == '.') {
          ++local_pos;
          if (str[local_pos] == '*') {
            ++local_pos;

            size_t precision_index = parse_index(&local_pos);
            set_type_desc(precision_index, type_desc_from_type<int>());
            if (precision_index == index)
              return type_desc_from_type<int>();

          } else if (internal::isdigit(str[local_pos])) {
            while (internal::isdigit(str[local_pos]))
              ++local_pos;
          }
        }

        auto [lm, bw] = parse_length_modifier(&local_pos);

        // if we don't have an index for this conversion, then its position is
        // unknown and all this information is irrelevant. The rest of this
        // logic has been for skipping past this conversion properly to avoid
        // weirdness with %%.
        if (conv_index == 0) {
          if (str[local_pos] != '\0')
            ++local_pos;
          continue;
        }

        TypeDesc conv_size = type_desc_from_type<void>();
        switch (str[local_pos]) {
        case ('%'):
          conv_size = type_desc_from_type<void>();
          break;
        case ('c'):
          conv_size = type_desc_from_type<int>();
          break;
        case ('d'):
        case ('i'):
        case ('o'):
        case ('x'):
        case ('X'):
        case ('u'):
        case ('b'):
        case ('B'):
          switch (lm) {
          case (LengthModifier::hh):
          case (LengthModifier::h):
          case (LengthModifier::none):
            conv_size = type_desc_from_type<int>();
            break;
          case (LengthModifier::l):
            conv_size = type_desc_from_type<long>();
            break;
          case (LengthModifier::ll):
          case (LengthModifier::L): // This isn't in the standard, but is in
                                    // other libc implementations.
            conv_size = type_desc_from_type<long long>();
            break;
          case (LengthModifier::j):
            conv_size = type_desc_from_type<intmax_t>();
            break;
          case (LengthModifier::z):
            conv_size = type_desc_from_type<size_t>();
            break;
          case (LengthModifier::t):
            conv_size = type_desc_from_type<ptrdiff_t>();
            break;
          case (LengthModifier::w):
          case (LengthModifier::wf):
            if (bw <= INT_WIDTH) {
              conv_size = type_desc_from_type<int>();
            } else if (bw <= LONG_WIDTH) {
              conv_size = type_desc_from_type<long>();
            } else if (bw <= LLONG_WIDTH) {
              conv_size = type_desc_from_type<long long>();
            } else {
              conv_size = type_desc_from_type<intmax_t>();
            }
            break;
          }
          break;
#ifndef LIBC_COPT_PRINTF_DISABLE_FLOAT
        case ('f'):
        case ('F'):
        case ('e'):
        case ('E'):
        case ('a'):
        case ('A'):
        case ('g'):
        case ('G'):
          if (lm != LengthModifier::L)
            conv_size = type_desc_from_type<double>();
          else
            conv_size = type_desc_from_type<long double>();
          break;
#endif // LIBC_COPT_PRINTF_DISABLE_FLOAT
#ifdef LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT
        // Capitalization represents sign, but we only need to get the right
        // bitwidth here so we ignore that.
        case ('r'):
        case ('R'):
          conv_size = type_desc_from_type<uint32_t>();
          break;
        case ('k'):
        case ('K'):
          if (lm == LengthModifier::l) {
            conv_size = type_desc_from_type<uint64_t>();
          } else {
            conv_size = type_desc_from_type<uint32_t>();
          }
          break;
#endif // LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT
#ifndef LIBC_COPT_PRINTF_DISABLE_WRITE_INT
        case ('n'):
#endif // LIBC_COPT_PRINTF_DISABLE_WRITE_INT
        case ('p'):
        case ('s'):
          conv_size = type_desc_from_type<void *>();
          break;
        default:
          conv_size = type_desc_from_type<int>();
          break;
        }

        set_type_desc(conv_index, conv_size);
        if (conv_index == index)
          return conv_size;
      }
      // If the end of the format section is on the '\0'. This means we need to
      // not advance the local_pos.
      if (str[local_pos] != '\0')
        ++local_pos;
    }

    // If there is no size for the requested index, then it's unknown. Return
    // void.
    return type_desc_from_type<void>();
  }

#endif // LIBC_COPT_PRINTF_DISABLE_INDEX_MODE
};

} // namespace printf_core
} // namespace LIBC_NAMESPACE

#endif // LLVM_LIBC_SRC_STDIO_PRINTF_CORE_PARSER_H