diff options
Diffstat (limited to 'src/nvim/strings.c')
| -rw-r--r-- | src/nvim/strings.c | 847 | 
1 files changed, 847 insertions, 0 deletions
| diff --git a/src/nvim/strings.c b/src/nvim/strings.c index b38d4f8a58..5ee29b9172 100644 --- a/src/nvim/strings.c +++ b/src/nvim/strings.c @@ -1,7 +1,10 @@  #include <inttypes.h>  #include <stdbool.h>  #include <string.h> +#include <math.h> +#include <assert.h> +#include "nvim/assert.h"  #include "nvim/vim.h"  #include "nvim/ascii.h"  #include "nvim/strings.h" @@ -541,3 +544,847 @@ char_u *concat_str(const char_u *restrict str1, const char_u *restrict str2)    return dest;  } + +static const char *const e_printf = +    N_("E766: Insufficient arguments for printf()"); + +/// Get number argument from idxp entry in tvs +/// +/// Will give an error message for VimL entry with invalid type or for +/// insufficient entries. +/// +/// @param[in]  tvs  List of VimL values. List is terminated by VAR_UNKNOWN +///                  value. +/// @param[in,out]  idxp  Index in a list. Will be incremented. Indexing starts +///                       at 1. +/// +/// @return Number value or 0 in case of error. +static varnumber_T tv_nr(typval_T *tvs, int *idxp) +  FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ +  int idx = *idxp - 1; +  varnumber_T n = 0; + +  if (tvs[idx].v_type == VAR_UNKNOWN) { +    EMSG(_(e_printf)); +  } else { +    (*idxp)++; +    int err = false; +    n = (varnumber_T)get_tv_number_chk(&tvs[idx], &err); +    if (err) { +      n = 0; +    } +  } +  return n; +} + +/// Get string argument from idxp entry in tvs +/// +/// Will give an error message for VimL entry with invalid type or for +/// insufficient entries. +/// +/// @param[in]  tvs  List of VimL values. List is terminated by VAR_UNKNOWN +///                  value. +/// @param[in,out]  idxp  Index in a list. Will be incremented. +/// +/// @return String value or NULL in case of error. +static char *tv_str(typval_T *tvs, int *idxp) +  FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ +  int idx = *idxp - 1; +  char        *s = NULL; + +  if (tvs[idx].v_type == VAR_UNKNOWN) { +    EMSG(_(e_printf)); +  } else { +    (*idxp)++; +    s = (char *)get_tv_string_chk(&tvs[idx]); +  } +  return s; +} + +/// Get pointer argument from the next entry in tvs +/// +/// Will give an error message for VimL entry with invalid type or for +/// insufficient entries. +/// +/// @param[in]  tvs  List of typval_T values. +/// @param[in,out]  idxp  Pointer to the index of the current value. +/// +/// @return Pointer stored in typval_T or NULL. +static const void *tv_ptr(const typval_T *const tvs, int *const idxp) +  FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ +#define OFF(attr) offsetof(union typval_vval_union, attr) +  STATIC_ASSERT( +      OFF(v_string) == OFF(v_list) +      && OFF(v_string) == OFF(v_dict) +      && OFF(v_string) == OFF(v_partial) +      && sizeof(tvs[0].vval.v_string) == sizeof(tvs[0].vval.v_list) +      && sizeof(tvs[0].vval.v_string) == sizeof(tvs[0].vval.v_dict) +      && sizeof(tvs[0].vval.v_string) == sizeof(tvs[0].vval.v_partial), +      "Strings, dictionaries, lists and partials are expected to be pointers, " +      "so that all three of them can be accessed via v_string"); +#undef OFF +  const int idx = *idxp - 1; +  if (tvs[idx].v_type == VAR_UNKNOWN) { +    EMSG(_(e_printf)); +    return NULL; +  } else { +    (*idxp)++; +    return tvs[idx].vval.v_string; +  } +} + +/// Get float argument from idxp entry in tvs +/// +/// Will give an error message for VimL entry with invalid type or for +/// insufficient entries. +/// +/// @param[in]  tvs  List of VimL values. List is terminated by VAR_UNKNOWN +///                  value. +/// @param[in,out]  idxp  Index in a list. Will be incremented. +/// +/// @return Floating-point value or zero in case of error. +static float_T tv_float(typval_T *const tvs, int *const idxp) +  FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ +  int idx = *idxp - 1; +  float_T f = 0; + +  if (tvs[idx].v_type == VAR_UNKNOWN) { +    EMSG(_(e_printf)); +  } else { +    (*idxp)++; +    if (tvs[idx].v_type == VAR_FLOAT) { +      f = tvs[idx].vval.v_float; +    } else if (tvs[idx].v_type == VAR_NUMBER) { +      f = tvs[idx].vval.v_number; +    } else { +      EMSG(_("E807: Expected Float argument for printf()")); +    } +  } +  return f; +} + +// This code was included to provide a portable vsnprintf() and snprintf(). +// Some systems may provide their own, but we always use this one for +// consistency. +// +// This code is based on snprintf.c - a portable implementation of snprintf +// by Mark Martinec <mark.martinec@ijs.si>, Version 2.2, 2000-10-06. +// Included with permission.  It was heavily modified to fit in Vim. +// The original code, including useful comments, can be found here: +// +//     http://www.ijs.si/software/snprintf/ +// +// This snprintf() only supports the following conversion specifiers: +// s, c, b, B, d, u, o, x, X, p  (and synonyms: i, D, U, O - see below) +// with flags: '-', '+', ' ', '0' and '#'. +// An asterisk is supported for field width as well as precision. +// +// Limited support for floating point was added: 'f', 'e', 'E', 'g', 'G'. +// +// Length modifiers 'h' (short int), 'l' (long int) and "ll" (long long int) are +// supported. +// +// The locale is not used, the string is used as a byte string.  This is only +// relevant for double-byte encodings where the second byte may be '%'. +// +// It is permitted for "str_m" to be zero, and it is permitted to specify NULL +// pointer for resulting string argument if "str_m" is zero (as per ISO C99). +// +// The return value is the number of characters which would be generated +// for the given input, excluding the trailing NUL. If this value +// is greater or equal to "str_m", not all characters from the result +// have been stored in str, output bytes beyond the ("str_m"-1) -th character +// are discarded. If "str_m" is greater than zero it is guaranteed +// the resulting string will be NUL-terminated. + +// vim_vsnprintf() can be invoked with either "va_list" or a list of +// "typval_T".  When the latter is not used it must be NULL. + +/// Append a formatted value to the string +/// +/// @see vim_vsnprintf(). +int vim_snprintf_add(char *str, size_t str_m, char *fmt, ...) +{ +  const size_t len = strlen(str); +  size_t space; + +  if (str_m <= len) { +    space = 0; +  } else { +    space = str_m - len; +  } +  va_list ap; +  va_start(ap, fmt); +  const int str_l = vim_vsnprintf(str + len, space, fmt, ap, NULL); +  va_end(ap); +  return str_l; +} + +/// Write formatted value to the string +/// +/// @param[out]  str  String to write to. +/// @param[in]  str_m  String length. +/// @param[in]  fmt  String format. +/// +/// @return Number of bytes excluding NUL byte that would be written to the +///         string if str_m was greater or equal to the return value. +int vim_snprintf(char *str, size_t str_m, const char *fmt, ...) +{ +  va_list ap; +  va_start(ap, fmt); +  const int str_l = vim_vsnprintf(str, str_m, fmt, ap, NULL); +  va_end(ap); +  return str_l; +} + + +/// Write formatted value to the string +/// +/// @param[out]  str  String to write to. +/// @param[in]  str_m  String length. +/// @param[in]  fmt  String format. +/// @param[in]  ap  Values that should be formatted. Ignored if tvs is not NULL. +/// @param[in]  tvs  Values that should be formatted, for printf() VimL +///                  function. Must be NULL in other cases. +/// +/// @return Number of bytes excluding NUL byte that would be written to the +///         string if str_m was greater or equal to the return value. +int vim_vsnprintf(char *str, size_t str_m, const char *fmt, va_list ap, +                  typval_T *const tvs) +{ +  size_t str_l = 0; +  bool str_avail = str_l < str_m; +  const char *p = fmt; +  int arg_idx = 1; + +  if (!p) { +    p = ""; +  } +  while (*p) { +    if (*p != '%') { +      // copy up to the next '%' or NUL without any changes +      size_t n = (size_t)(xstrchrnul(p + 1, '%') - p); +      if (str_avail) { +        size_t avail = str_m - str_l; +        memmove(str + str_l, p, MIN(n, avail)); +        str_avail = n < avail; +      } +      p += n; +      assert(n <= SIZE_MAX - str_l); +      str_l += n; +    } else { +      size_t min_field_width = 0, precision = 0; +      int zero_padding = 0, precision_specified = 0, justify_left = 0; +      int alternate_form = 0, force_sign = 0; + +      // if both ' ' and '+' flags appear, ' ' flag should be ignored +      int space_for_positive = 1; + +      // allowed values: \0, h, l, 2 (for ll), z, L +      char length_modifier = '\0'; + +      // temporary buffer for simple numeric->string conversion +# define TMP_LEN 350    // 1e308 seems reasonable as the maximum printable +      char tmp[TMP_LEN]; + +      // string address in case of string argument +      const char *str_arg = NULL; + +      // natural field width of arg without padding and sign +      size_t str_arg_l; + +      // unsigned char argument value (only defined for c conversion); +      // standard explicitly states the char argument for the c +      // conversion is unsigned +      unsigned char uchar_arg; + +      // number of zeros to be inserted for numeric conversions as +      // required by the precision or minimal field width +      size_t number_of_zeros_to_pad = 0; + +      // index into tmp where zero padding is to be inserted +      size_t zero_padding_insertion_ind = 0; + +      // current conversion specifier character +      char fmt_spec = '\0'; + +      p++;  // skip '%' + +      // parse flags +      while (true) { +        switch (*p) { +          case '0': zero_padding = 1; p++; continue; +          case '-': justify_left = 1; p++; continue; +                    // if both '0' and '-' flags appear, '0' should be ignored +          case '+': force_sign = 1; space_for_positive = 0; p++; continue; +          case ' ': force_sign = 1; p++; continue; +                    // if both ' ' and '+' flags appear, ' ' should be ignored +          case '#': alternate_form = 1; p++; continue; +          case '\'': p++; continue; +          default: break; +        } +        break; +      } + +      // parse field width +      if (*p == '*') { +        p++; +        const int j = tvs ? (int)tv_nr(tvs, &arg_idx) : va_arg(ap, int); +        if (j >= 0) { +          min_field_width = (size_t)j; +        } else { +          min_field_width = (size_t)-j; +          justify_left = 1; +        } +      } else if (ascii_isdigit((int)(*p))) { +        // size_t could be wider than unsigned int; make sure we treat +        // argument like common implementations do +        unsigned int uj = (unsigned)(*p++ - '0'); + +        while (ascii_isdigit((int)(*p))) { +          uj = 10 * uj + (unsigned int)(*p++ - '0'); +        } +        min_field_width = uj; +      } + +      // parse precision +      if (*p == '.') { +        p++; +        precision_specified = 1; +        if (*p == '*') { +          const int j = tvs ? (int)tv_nr(tvs, &arg_idx) : va_arg(ap, int); +          p++; +          if (j >= 0) { +            precision = (size_t)j; +          } else { +            precision_specified = 0; +            precision = 0; +          } +        } else if (ascii_isdigit((int)(*p))) { +          // size_t could be wider than unsigned int; make sure we +          // treat argument like common implementations do +          unsigned int uj = (unsigned)(*p++ - '0'); + +          while (ascii_isdigit((int)(*p))) { +            uj = 10 * uj + (unsigned int)(*p++ - '0'); +          } +          precision = uj; +        } +      } + +      // parse 'h', 'l', 'll' and 'z' length modifiers +      if (*p == 'h' || *p == 'l' || *p == 'z') { +        length_modifier = *p; +        p++; +        if (length_modifier == 'l' && *p == 'l') {  // ll, encoded as 2 +          length_modifier = '2'; +          p++; +        } +      } + +      fmt_spec = *p; + +      // common synonyms +      switch (fmt_spec) { +        case 'i': fmt_spec = 'd'; break; +        case 'D': fmt_spec = 'd'; length_modifier = 'l'; break; +        case 'U': fmt_spec = 'u'; length_modifier = 'l'; break; +        case 'O': fmt_spec = 'o'; length_modifier = 'l'; break; +        case 'F': fmt_spec = 'f'; break; +        default: break; +      } + +      // get parameter value, do initial processing +      switch (fmt_spec) { +        // '%' and 'c' behave similar to 's' regarding flags and field widths +        case '%': case 'c': case 's': case 'S': +          str_arg_l = 1; +          switch (fmt_spec) { +            case '%': +              str_arg = p; +              break; + +            case 'c': { +              const int j = tvs ? (int)tv_nr(tvs, &arg_idx) : va_arg(ap, int); +              // standard demands unsigned char +              uchar_arg = (unsigned char)j; +              str_arg = (char *)&uchar_arg; +              break; +            } + +            case 's': +            case 'S': +              str_arg = tvs ? tv_str(tvs, &arg_idx) : va_arg(ap, char *); +              if (!str_arg) { +                str_arg = "[NULL]"; +                str_arg_l = 6; +              } else if (!precision_specified) { +                // make sure not to address string beyond the specified +                // precision +                str_arg_l = strlen(str_arg); +              } else if (precision == 0) { +                // truncate string if necessary as requested by precision +                str_arg_l = 0; +              } else { +                // memchr on HP does not like n > 2^31 +                // TODO(elmart): check if this still holds / is relevant +                str_arg_l = (size_t)((char *)xmemscan(str_arg, +                                                      NUL, +                                                      MIN(precision, +                                                          0x7fffffff)) +                                     - str_arg); +              } +              if (fmt_spec == 'S') { +                if (min_field_width != 0) { +                  min_field_width += (strlen(str_arg) +                                      - mb_string2cells((char_u *)str_arg)); +                } +                if (precision) { +                  const char *p1 = str_arg; +                  for (size_t i = 0; i < precision && *p1; i++) { +                    p1 += mb_ptr2len((const char_u *)p1); +                  } +                  str_arg_l = precision = (size_t)(p1 - str_arg); +                } +              } +              break; + +            default: +              break; +          } +          break; + +        case 'd': +        case 'u': +        case 'b': case 'B': +        case 'o': +        case 'x': case 'X': +        case 'p': { +          // u, b, B, o, x, X and p conversion specifiers imply +          // the value is unsigned; d implies a signed value + +          // 0 if numeric argument is zero (or if pointer is NULL for 'p'), +          // +1 if greater than zero (or non NULL for 'p'), +          // -1 if negative (unsigned argument is never negative) +          int arg_sign = 0; + +          intmax_t arg = 0; +          uintmax_t uarg = 0; + +          // only defined for p conversion +          const void *ptr_arg = NULL; + +          if (fmt_spec == 'p') { +            length_modifier = '\0'; +            ptr_arg = tvs ? tv_ptr(tvs, &arg_idx) : va_arg(ap, void *); +            if (ptr_arg) { +              arg_sign = 1; +            } +          } else if (fmt_spec == 'd') { +            // signed +            switch (length_modifier) { +              case '\0': +              case 'h': { +                // char and short arguments are passed as int +                arg = (tvs ? (int)tv_nr(tvs, &arg_idx) : va_arg(ap, int)); +                break; +              } +              case 'l': { +                arg = (tvs ? (long)tv_nr(tvs, &arg_idx) : va_arg(ap, long)); +                break; +              } +              case '2': { +                arg = ( +                    tvs +                    ? (long long)tv_nr(tvs, &arg_idx)  // NOLINT (runtime/int) +                    : va_arg(ap, long long));  // NOLINT (runtime/int) +                break; +              } +              case 'z': { +                arg = (tvs +                       ? (ptrdiff_t)tv_nr(tvs, &arg_idx) +                       : va_arg(ap, ptrdiff_t)); +                break; +              } +            } +            if (arg > 0) { +              arg_sign =  1; +            } else if (arg < 0) { +              arg_sign = -1; +            } +          } else { +            // unsigned +            switch (length_modifier) { +              case '\0': +              case 'h': { +                uarg = (tvs +                        ? (unsigned)tv_nr(tvs, &arg_idx) +                        : va_arg(ap, unsigned)); +                break; +              } +              case 'l': { +                uarg = (tvs +                        ? (unsigned long)tv_nr(tvs, &arg_idx) +                        : va_arg(ap, unsigned long)); +                break; +              } +              case '2': { +                uarg = (uintmax_t)(unsigned long long)(  // NOLINT (runtime/int) +                    tvs +                    ? ((unsigned long long)  // NOLINT (runtime/int) +                       tv_nr(tvs, &arg_idx)) +                    : va_arg(ap, unsigned long long));  // NOLINT (runtime/int) +                break; +              } +              case 'z': { +                uarg = (tvs +                        ? (size_t)tv_nr(tvs, &arg_idx) +                        : va_arg(ap, size_t)); +                break; +              } +            } +            arg_sign = (uarg != 0); +          } + +          str_arg = tmp; +          str_arg_l = 0; + +          // For d, i, u, o, x, and X conversions, if precision is specified, +          // '0' flag should be ignored. This is so with Solaris 2.6, Digital +          // UNIX 4.0, HPUX 10, Linux, FreeBSD, NetBSD; but not with Perl. +          if (precision_specified) { +            zero_padding = 0; +          } + +          if (fmt_spec == 'd') { +            if (force_sign && arg_sign >= 0) { +              tmp[str_arg_l++] = space_for_positive ? ' ' : '+'; +            } +            // leave negative numbers for snprintf to handle, to +            // avoid handling tricky cases like (short int)-32768 +          } else if (alternate_form) { +            if (arg_sign != 0 && (fmt_spec == 'x' || fmt_spec == 'X' +                                  || fmt_spec == 'b' || fmt_spec == 'B')) { +              tmp[str_arg_l++] = '0'; +              tmp[str_arg_l++] = fmt_spec; +            } +            // alternate form should have no effect for p * conversion, but ... +          } + +          zero_padding_insertion_ind = str_arg_l; +          if (!precision_specified) { +            precision = 1;  // default precision is 1 +          } +          if (precision == 0 && arg_sign == 0) { +            // when zero value is formatted with an explicit precision 0, +            // resulting formatted string is empty (d, i, u, b, B, o, x, X, p) +          } else { +            switch (fmt_spec) { +              case 'p': {  // pointer +                str_arg_l += (size_t)snprintf(tmp + str_arg_l, +                                              sizeof(tmp) - str_arg_l, +                                              "%p", ptr_arg); +                break; +              } +              case 'd': {  // signed +                str_arg_l += (size_t)snprintf(tmp + str_arg_l, +                                              sizeof(tmp) - str_arg_l, +                                              "%" PRIdMAX, arg); +                break; +              } +              case 'b': case 'B': {  // binary +                size_t bits = 0; +                for (bits = sizeof(uintmax_t) * 8; bits > 0; bits--) { +                  if ((uarg >> (bits - 1)) & 0x1) { +                    break; +                  } +                } + +                while (bits > 0) { +                  tmp[str_arg_l++] = ((uarg >> --bits) & 0x1) ? '1' : '0'; +                } +                break; +              } +              default: {  // unsigned +                // construct a simple format string for snprintf +                char f[] = "%" PRIuMAX; +                f[sizeof("%" PRIuMAX) - 1 - 1] = fmt_spec; +                assert(PRIuMAX[sizeof(PRIuMAX) - 1 - 1] == 'u'); +                str_arg_l += (size_t)snprintf(tmp + str_arg_l, +                                              sizeof(tmp) - str_arg_l, +                                              f, uarg); +                break; +              } +              assert(str_arg_l < sizeof(tmp)); +            } + +            // include the optional minus sign and possible "0x" in the region +            // before the zero padding insertion point +            if (zero_padding_insertion_ind < str_arg_l +                && tmp[zero_padding_insertion_ind] == '-') { +              zero_padding_insertion_ind++; +            } +            if (zero_padding_insertion_ind + 1 < str_arg_l +                && tmp[zero_padding_insertion_ind]   == '0' +                && (tmp[zero_padding_insertion_ind + 1] == 'x' +                    || tmp[zero_padding_insertion_ind + 1] == 'X' +                    || tmp[zero_padding_insertion_ind + 1] == 'b' +                    || tmp[zero_padding_insertion_ind + 1] == 'B')) { +              zero_padding_insertion_ind += 2; +            } +          } + +          { +            size_t num_of_digits = str_arg_l - zero_padding_insertion_ind; + +            if (alternate_form && fmt_spec == 'o' +                // unless zero is already the first character +                && !(zero_padding_insertion_ind < str_arg_l +                     && tmp[zero_padding_insertion_ind] == '0')) { +              // assure leading zero for alternate-form octal numbers +              if (!precision_specified +                  || precision < num_of_digits + 1) { +                // precision is increased to force the first character to be +                // zero, except if a zero value is formatted with an explicit +                // precision of zero +                precision = num_of_digits + 1; +              } +            } +            // zero padding to specified precision? +            if (num_of_digits < precision) { +              number_of_zeros_to_pad = precision - num_of_digits; +            } +          } +          // zero padding to specified minimal field width? +          if (!justify_left && zero_padding) { +            const int n = (int)(min_field_width - (str_arg_l +                                                   + number_of_zeros_to_pad)); +            if (n > 0) { +              number_of_zeros_to_pad += (size_t)n; +            } +          } +          break; +        } + +        case 'f': +        case 'e': +        case 'E': +        case 'g': +        case 'G': +          { +            // floating point +            char format[40]; +            int remove_trailing_zeroes = false; + +            double f = tvs ? tv_float(tvs, &arg_idx) : va_arg(ap, double); +            double abs_f = f < 0 ? -f : f; + +            if (fmt_spec == 'g' || fmt_spec == 'G') { +              // can't use %g directly, cause it prints "1.0" as "1" +              if ((abs_f >= 0.001 && abs_f < 10000000.0) || abs_f == 0.0) { +                fmt_spec = 'f'; +              } else { +                fmt_spec = fmt_spec == 'g' ? 'e' : 'E'; +              } +              remove_trailing_zeroes = true; +            } + +            if (fmt_spec == 'f' && abs_f > 1.0e307) { +              // avoid a buffer overflow +              memmove(tmp, "inf", sizeof("inf")); +              str_arg_l = sizeof("inf") - 1; +            } else { +              format[0] = '%'; +              int l = 1; +              if (precision_specified) { +                size_t max_prec = TMP_LEN - 10; + +                // make sure we don't get more digits than we have room for +                if (fmt_spec == 'f' && abs_f > 1.0) { +                  max_prec -= (size_t)log10(abs_f); +                } +                if (precision > max_prec) { +                  precision = max_prec; +                } +                l += snprintf(format + 1, sizeof(format) - 1, ".%d", +                              (int)precision); +              } +              format[l] = fmt_spec; +              format[l + 1] = NUL; +              assert(l + 1 < (int)sizeof(format)); +              str_arg_l = (size_t)snprintf(tmp, sizeof(tmp), format, f); +              assert(str_arg_l < sizeof(tmp)); + +              if (remove_trailing_zeroes) { +                int i; +                char *tp; + +                // using %g or %G: remove superfluous zeroes +                if (fmt_spec == 'f') { +                  tp = tmp + str_arg_l - 1; +                } else { +                  tp = (char *)vim_strchr((char_u *)tmp, +                                          fmt_spec == 'e' ? 'e' : 'E'); +                  if (tp) { +                    // remove superfluous '+' and leading zeroes from exponent +                    if (tp[1] == '+') { +                      // change "1.0e+07" to "1.0e07" +                      STRMOVE(tp + 1, tp + 2); +                      str_arg_l--; +                    } +                    i = (tp[1] == '-') ? 2 : 1; +                    while (tp[i] == '0') { +                      // change "1.0e07" to "1.0e7" +                      STRMOVE(tp + i, tp + i + 1); +                      str_arg_l--; +                    } +                    tp--; +                  } +                } + +                if (tp != NULL && !precision_specified) { +                  // remove trailing zeroes, but keep the one just after a dot +                  while (tp > tmp + 2 && *tp == '0' && tp[-1] != '.') { +                    STRMOVE(tp, tp + 1); +                    tp--; +                    str_arg_l--; +                  } +                } +              } else { +                // Be consistent: some printf("%e") use 1.0e+12 and some +                // 1.0e+012; remove one zero in the last case. +                char *tp = (char *)vim_strchr((char_u *)tmp, +                                              fmt_spec == 'e' ? 'e' : 'E'); +                if (tp && (tp[1] == '+' || tp[1] == '-') && tp[2] == '0' +                    && ascii_isdigit(tp[3]) && ascii_isdigit(tp[4])) { +                  STRMOVE(tp + 2, tp + 3); +                  str_arg_l--; +                } +              } +            } +            str_arg = tmp; +            break; +          } + +        default: +          // unrecognized conversion specifier, keep format string as-is +          zero_padding = 0;  // turn zero padding off for non-numeric conversion +          justify_left = 1; +          min_field_width = 0;  // reset flags + +          // discard the unrecognized conversion, just keep +          // the unrecognized conversion character +          str_arg = p; +          str_arg_l = 0; +          if (*p) { +            str_arg_l++;  // include invalid conversion specifier +          } +          // unchanged if not at end-of-string +          break; +      } + +      if (*p) { +        p++;  // step over the just processed conversion specifier +      } + +      // insert padding to the left as requested by min_field_width; +      // this does not include the zero padding in case of numerical conversions +      if (!justify_left) { +        assert(str_arg_l <= SIZE_MAX - number_of_zeros_to_pad); +        if (min_field_width > str_arg_l + number_of_zeros_to_pad) { +          // left padding with blank or zero +          size_t pn = min_field_width - (str_arg_l + number_of_zeros_to_pad); +          if (str_avail) { +            size_t avail = str_m - str_l; +            memset(str + str_l, zero_padding ? '0' : ' ', MIN(pn, avail)); +            str_avail = pn < avail; +          } +          assert(pn <= SIZE_MAX - str_l); +          str_l += pn; +        } +      } + +      // zero padding as requested by the precision or by the minimal +      // field width for numeric conversions required? +      if (number_of_zeros_to_pad == 0) { +        // will not copy first part of numeric right now, +        // force it to be copied later in its entirety +        zero_padding_insertion_ind = 0; +      } else { +        // insert first part of numerics (sign or '0x') before zero padding +        if (zero_padding_insertion_ind > 0) { +          size_t zn = zero_padding_insertion_ind; +          if (str_avail) { +            size_t avail = str_m - str_l; +            memmove(str + str_l, str_arg, MIN(zn, avail)); +            str_avail = zn < avail; +          } +          assert(zn <= SIZE_MAX - str_l); +          str_l += zn; +        } + +        // insert zero padding as requested by precision or min field width +        if (number_of_zeros_to_pad > 0) { +          size_t zn = number_of_zeros_to_pad; +          if (str_avail) { +            size_t avail = str_m - str_l; +            memset(str + str_l, '0', MIN(zn, avail)); +            str_avail = zn < avail; +          } +          assert(zn <= SIZE_MAX - str_l); +          str_l += zn; +        } +      } + +      // insert formatted string +      // (or as-is conversion specifier for unknown conversions) +      if (str_arg_l > zero_padding_insertion_ind) { +        size_t sn = str_arg_l - zero_padding_insertion_ind; +        if (str_avail) { +          size_t avail = str_m - str_l; +          memmove(str + str_l, +                  str_arg + zero_padding_insertion_ind, +                  MIN(sn, avail)); +          str_avail = sn < avail; +        } +        assert(sn <= SIZE_MAX - str_l); +        str_l += sn; +      } + +      // insert right padding +      if (justify_left) { +        assert(str_arg_l <= SIZE_MAX - number_of_zeros_to_pad); +        if (min_field_width > str_arg_l + number_of_zeros_to_pad) { +          // right blank padding to the field width +          size_t pn = min_field_width - (str_arg_l + number_of_zeros_to_pad); +          if (str_avail) { +            size_t avail = str_m - str_l; +            memset(str + str_l, ' ', MIN(pn, avail)); +            str_avail = pn < avail; +          } +          assert(pn <= SIZE_MAX - str_l); +          str_l += pn; +        } +      } +    } +  } + +  if (str_m > 0) { +    // make sure the string is nul-terminated even at the expense of +    // overwriting the last character (shouldn't happen, but just in case) +    str[str_l <= str_m - 1 ? str_l : str_m - 1] = '\0'; +  } + +  if (tvs && tvs[arg_idx - 1].v_type != VAR_UNKNOWN) { +    EMSG(_("E767: Too many arguments to printf()")); +  } + +  // return the number of characters formatted (excluding trailing nul +  // character); that is, the number of characters that would have been +  // written to the buffer if it were large enough. +  return (int)str_l; +} | 
