diff options
author | ZyX <kp-pav@yandex.ru> | 2016-08-21 01:47:28 +0300 |
---|---|---|
committer | ZyX <kp-pav@yandex.ru> | 2017-02-14 00:53:03 +0300 |
commit | a429235b6da4d6c8bb0a80efa0ee5aa0bcc430e8 (patch) | |
tree | 4a21a13c48860a4c23f5e45bc047ee78bc2ca3cc /src/nvim/strings.c | |
parent | 4a511de881efcf9b759c7babdeb0e31242d62c2e (diff) | |
download | rneovim-a429235b6da4d6c8bb0a80efa0ee5aa0bcc430e8.tar.gz rneovim-a429235b6da4d6c8bb0a80efa0ee5aa0bcc430e8.tar.bz2 rneovim-a429235b6da4d6c8bb0a80efa0ee5aa0bcc430e8.zip |
message,strings: Move vim_*printf functions to strings.c
Allows eval/typval.h to #include message.h.
Diffstat (limited to 'src/nvim/strings.c')
-rw-r--r-- | src/nvim/strings.c | 850 |
1 files changed, 850 insertions, 0 deletions
diff --git a/src/nvim/strings.c b/src/nvim/strings.c index b38d4f8a58..da203cc9bd 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,850 @@ 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 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. 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 { + // construct a simple format string for snprintf + char f[] = "%" PRIdMAX; + f[sizeof("%" PRIdMAX) - 1 - 1] = fmt_spec; + + switch (fmt_spec) { + case 'p': { + str_arg_l += (size_t)snprintf(tmp + str_arg_l, + sizeof(tmp) - str_arg_l, + f, ptr_arg); + break; + } + case 'd': { + // signed + str_arg_l += (size_t)snprintf(tmp + str_arg_l, + sizeof(tmp) - str_arg_l, + f, 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 + 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; +} |