//===-- 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/limits.h"
#include "src/__support/CPP/optional.h"
#include "src/__support/CPP/type_traits.h"
#include "src/__support/macros/config.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
#ifndef LIBC_COPT_PRINTF_DISABLE_STRERROR
#include "src/errno/libc_errno.h"
#endif // LIBC_COPT_PRINTF_DISABLE_STRERROR

namespace LIBC_NAMESPACE_DECL {
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 == INT_MIN) ? INT_MAX : -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 <= cpp::numeric_limits<unsigned int>::digits) {
            WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, int, conv_index);
          } else if (bw <= cpp::numeric_limits<unsigned long>::digits) {
            WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, long, conv_index);
          } else if (bw <= cpp::numeric_limits<unsigned long long>::digits) {
            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_STRERROR
      case ('m'):
        // %m is an odd conversion in that it doesn't consume an argument, it
        // just takes the current value of errno as its argument.
        section.conv_val_raw = static_cast<int>(libc_errno);
        break;
#endif // LIBC_COPT_PRINTF_DISABLE_STRERROR
#ifndef LIBC_COPT_PRINTF_DISABLE_WRITE_INT
      case ('n'): // Intentional fallthrough
#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 indices 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 <= cpp::numeric_limits<unsigned int>::digits) {
              conv_size = type_desc_from_type<int>();
            } else if (bw <= cpp::numeric_limits<unsigned long>::digits) {
              conv_size = type_desc_from_type<long>();
            } else if (bw <= cpp::numeric_limits<unsigned long long>::digits) {
              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_DECL

#endif // LLVM_LIBC_SRC_STDIO_PRINTF_CORE_PARSER_H
