diff src/strings.c @ 25567:0082503ff2ff v8.2.3320

patch 8.2.3320: some local functions are not static Commit: https://github.com/vim/vim/commit/8ee52affe7fd4daa03e002bc06611f0a8c3bcd5b Author: Yegappan Lakshmanan <yegappan@yahoo.com> Date: Mon Aug 9 19:59:06 2021 +0200 patch 8.2.3320: some local functions are not static Problem: Some local functions are not static. Solution: Add "static". Move snprintf() related code to strings.c. (Yegappan Lakshmanan, closes #8734)
author Bram Moolenaar <Bram@vim.org>
date Mon, 09 Aug 2021 20:00:06 +0200
parents e8e2c4d33b9b
children 000b37efd5fa
line wrap: on
line diff
--- a/src/strings.c
+++ b/src/strings.c
@@ -11,6 +11,7 @@
  * strings.c: string manipulation functions
  */
 
+#define USING_FLOAT_STUFF
 #include "vim.h"
 
 /*
@@ -77,10 +78,8 @@ vim_strsave_escaped_ext(
     unsigned	length;
     int		l;
 
-    /*
-     * First count the number of backslashes required.
-     * Then allocate the memory and insert them.
-     */
+    // First count the number of backslashes required.
+    // Then allocate the memory and insert them.
     length = 1;				// count the trailing NUL
     for (p = string; *p; p++)
     {
@@ -1265,10 +1264,8 @@ f_strcharpart(typval_T *argvars, typval_
 	    len = slen - nbyte;    // default: all bytes that are available.
     }
 
-    /*
-     * Only return the overlap between the specified part and the actual
-     * string.
-     */
+    // Only return the overlap between the specified part and the actual
+    // string.
     if (nbyte < 0)
     {
 	len += nbyte;
@@ -1668,3 +1665,1112 @@ f_trim(typval_T *argvars, typval_T *rett
 }
 
 #endif
+
+#if defined(FEAT_EVAL)
+static char *e_printf = N_("E766: Insufficient arguments for printf()");
+
+/*
+ * Get number argument from "idxp" entry in "tvs".  First entry is 1.
+ */
+    static varnumber_T
+tv_nr(typval_T *tvs, int *idxp)
+{
+    int		idx = *idxp - 1;
+    varnumber_T	n = 0;
+    int		err = FALSE;
+
+    if (tvs[idx].v_type == VAR_UNKNOWN)
+	emsg(_(e_printf));
+    else
+    {
+	++*idxp;
+	n = tv_get_number_chk(&tvs[idx], &err);
+	if (err)
+	    n = 0;
+    }
+    return n;
+}
+
+/*
+ * Get string argument from "idxp" entry in "tvs".  First entry is 1.
+ * If "tofree" is NULL tv_get_string_chk() is used.  Some types (e.g. List)
+ * are not converted to a string.
+ * If "tofree" is not NULL echo_string() is used.  All types are converted to
+ * a string with the same format as ":echo".  The caller must free "*tofree".
+ * Returns NULL for an error.
+ */
+    static char *
+tv_str(typval_T *tvs, int *idxp, char_u **tofree)
+{
+    int		    idx = *idxp - 1;
+    char	    *s = NULL;
+    static char_u   numbuf[NUMBUFLEN];
+
+    if (tvs[idx].v_type == VAR_UNKNOWN)
+	emsg(_(e_printf));
+    else
+    {
+	++*idxp;
+	if (tofree != NULL)
+	    s = (char *)echo_string(&tvs[idx], tofree, numbuf, get_copyID());
+	else
+	    s = (char *)tv_get_string_chk(&tvs[idx]);
+    }
+    return s;
+}
+
+# ifdef FEAT_FLOAT
+/*
+ * Get float argument from "idxp" entry in "tvs".  First entry is 1.
+ */
+    static double
+tv_float(typval_T *tvs, int *idxp)
+{
+    int		idx = *idxp - 1;
+    double	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 = (double)tvs[idx].vval.v_number;
+	else
+	    emsg(_("E807: Expected Float argument for printf()"));
+    }
+    return f;
+}
+# endif
+#endif
+
+#ifdef FEAT_FLOAT
+/*
+ * Return the representation of infinity for printf() function:
+ * "-inf", "inf", "+inf", " inf", "-INF", "INF", "+INF" or " INF".
+ */
+    static const char *
+infinity_str(int positive,
+	     char fmt_spec,
+	     int force_sign,
+	     int space_for_positive)
+{
+    static const char *table[] =
+    {
+	"-inf", "inf", "+inf", " inf",
+	"-INF", "INF", "+INF", " INF"
+    };
+    int idx = positive * (1 + force_sign + force_sign * space_for_positive);
+
+    if (ASCII_ISUPPER(fmt_spec))
+	idx += 4;
+    return table[idx];
+}
+#endif
+
+/*
+ * 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, 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', 'F', 'e', 'E', 'g', 'G'.
+ *
+ * Length modifiers 'h' (short int) and 'l' (long int) and 'll' (long long int)
+ * are supported.  NOTE: for 'll' the argument is varnumber_T or uvarnumber_T.
+ *
+ * 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.
+ */
+
+/*
+ * When va_list is not supported we only define vim_snprintf().
+ *
+ * vim_vsnprintf_typval() can be invoked with either "va_list" or a list of
+ * "typval_T".  When the latter is not used it must be NULL.
+ */
+
+// When generating prototypes all of this is skipped, cproto doesn't
+// understand this.
+#ifndef PROTO
+
+// Like vim_vsnprintf() but append to the string.
+    int
+vim_snprintf_add(char *str, size_t str_m, const char *fmt, ...)
+{
+    va_list	ap;
+    int		str_l;
+    size_t	len = STRLEN(str);
+    size_t	space;
+
+    if (str_m <= len)
+	space = 0;
+    else
+	space = str_m - len;
+    va_start(ap, fmt);
+    str_l = vim_vsnprintf(str + len, space, fmt, ap);
+    va_end(ap);
+    return str_l;
+}
+
+    int
+vim_snprintf(char *str, size_t str_m, const char *fmt, ...)
+{
+    va_list	ap;
+    int		str_l;
+
+    va_start(ap, fmt);
+    str_l = vim_vsnprintf(str, str_m, fmt, ap);
+    va_end(ap);
+    return str_l;
+}
+
+    int
+vim_vsnprintf(
+    char	*str,
+    size_t	str_m,
+    const char	*fmt,
+    va_list	ap)
+{
+    return vim_vsnprintf_typval(str, str_m, fmt, ap, NULL);
+}
+
+    int
+vim_vsnprintf_typval(
+    char	*str,
+    size_t	str_m,
+    const char	*fmt,
+    va_list	ap,
+    typval_T	*tvs)
+{
+    size_t	str_l = 0;
+    const char	*p = fmt;
+    int		arg_idx = 1;
+
+    if (p == NULL)
+	p = "";
+    while (*p != NUL)
+    {
+	if (*p != '%')
+	{
+	    char    *q = strchr(p + 1, '%');
+	    size_t  n = (q == NULL) ? STRLEN(p) : (size_t)(q - p);
+
+	    // Copy up to the next '%' or NUL without any changes.
+	    if (str_l < str_m)
+	    {
+		size_t avail = str_m - str_l;
+
+		mch_memmove(str + str_l, p, n > avail ? avail : n);
+	    }
+	    p += n;
+	    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 the ' ' and '+' flags appear, the ' ' flag should be
+	    // ignored.
+	    int	    space_for_positive = 1;
+
+	    // allowed values: \0, h, l, L
+	    char    length_modifier = '\0';
+
+	    // temporary buffer for simple numeric->string conversion
+# if defined(FEAT_FLOAT)
+#  define TMP_LEN 350	// On my system 1e308 is the biggest number possible.
+			// That sounds reasonable to use as the maximum
+			// printable.
+# else
+#  define TMP_LEN 66
+# endif
+	    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.
+	    // N.B. 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';
+
+	    // buffer for 's' and 'S' specs
+	    char_u  *tofree = NULL;
+
+
+	    p++;  // skip '%'
+
+	    // parse flags
+	    while (*p == '0' || *p == '-' || *p == '+' || *p == ' '
+						   || *p == '#' || *p == '\'')
+	    {
+		switch (*p)
+		{
+		    case '0': zero_padding = 1; break;
+		    case '-': justify_left = 1; break;
+		    case '+': force_sign = 1; space_for_positive = 0; break;
+		    case ' ': force_sign = 1;
+			      // If both the ' ' and '+' flags appear, the ' '
+			      // flag should be ignored
+			      break;
+		    case '#': alternate_form = 1; break;
+		    case '\'': break;
+		}
+		p++;
+	    }
+	    // If the '0' and '-' flags both appear, the '0' flag should be
+	    // ignored.
+
+	    // parse field width
+	    if (*p == '*')
+	    {
+		int j;
+
+		p++;
+		j =
+# if defined(FEAT_EVAL)
+		    tvs != NULL ? tv_nr(tvs, &arg_idx) :
+# endif
+			va_arg(ap, int);
+		if (j >= 0)
+		    min_field_width = j;
+		else
+		{
+		    min_field_width = -j;
+		    justify_left = 1;
+		}
+	    }
+	    else if (VIM_ISDIGIT((int)(*p)))
+	    {
+		// size_t could be wider than unsigned int; make sure we treat
+		// argument like common implementations do
+		unsigned int uj = *p++ - '0';
+
+		while (VIM_ISDIGIT((int)(*p)))
+		    uj = 10 * uj + (unsigned int)(*p++ - '0');
+		min_field_width = uj;
+	    }
+
+	    // parse precision
+	    if (*p == '.')
+	    {
+		p++;
+		precision_specified = 1;
+		if (*p == '*')
+		{
+		    int j;
+
+		    j =
+# if defined(FEAT_EVAL)
+			tvs != NULL ? tv_nr(tvs, &arg_idx) :
+# endif
+			    va_arg(ap, int);
+		    p++;
+		    if (j >= 0)
+			precision = j;
+		    else
+		    {
+			precision_specified = 0;
+			precision = 0;
+		    }
+		}
+		else if (VIM_ISDIGIT((int)(*p)))
+		{
+		    // size_t could be wider than unsigned int; make sure we
+		    // treat argument like common implementations do
+		    unsigned int uj = *p++ - '0';
+
+		    while (VIM_ISDIGIT((int)(*p)))
+			uj = 10 * uj + (unsigned int)(*p++ - '0');
+		    precision = uj;
+		}
+	    }
+
+	    // parse 'h', 'l' and 'll' length modifiers
+	    if (*p == 'h' || *p == 'l')
+	    {
+		length_modifier = *p;
+		p++;
+		if (length_modifier == 'l' && *p == 'l')
+		{
+		    // double l = __int64 / varnumber_T
+		    length_modifier = 'L';
+		    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;
+		default: break;
+	    }
+
+# if defined(FEAT_EVAL)
+	    switch (fmt_spec)
+	    {
+		case 'd': case 'u': case 'o': case 'x': case 'X':
+		    if (tvs != NULL && length_modifier == '\0')
+			length_modifier = 'L';
+	    }
+# endif
+
+	    // 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':
+		    {
+			int j;
+
+			j =
+# if defined(FEAT_EVAL)
+			    tvs != NULL ? tv_nr(tvs, &arg_idx) :
+# endif
+				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 =
+# if defined(FEAT_EVAL)
+				tvs != NULL ? tv_str(tvs, &arg_idx, &tofree) :
+# endif
+				    va_arg(ap, char *);
+		    if (str_arg == NULL)
+		    {
+			str_arg = "[NULL]";
+			str_arg_l = 6;
+		    }
+		    // make sure not to address string beyond the specified
+		    // precision !!!
+		    else if (!precision_specified)
+			str_arg_l = strlen(str_arg);
+		    // truncate string if necessary as requested by precision
+		    else if (precision == 0)
+			str_arg_l = 0;
+		    else
+		    {
+			// Don't put the #if inside memchr(), it can be a
+			// macro.
+			// memchr on HP does not like n > 2^31  !!!
+			char *q = memchr(str_arg, '\0',
+				  precision <= (size_t)0x7fffffffL ? precision
+						       : (size_t)0x7fffffffL);
+			str_arg_l = (q == NULL) ? precision
+						      : (size_t)(q - str_arg);
+		    }
+		    if (fmt_spec == 'S')
+		    {
+			if (min_field_width != 0)
+			    min_field_width += STRLEN(str_arg)
+				     - mb_string2cells((char_u *)str_arg, -1);
+			if (precision)
+			{
+			    char_u  *p1;
+			    size_t  i = 0;
+
+			    for (p1 = (char_u *)str_arg; *p1;
+							  p1 += mb_ptr2len(p1))
+			    {
+				i += (size_t)mb_ptr2cells(p1);
+				if (i > precision)
+				    break;
+			    }
+			    str_arg_l = precision = p1 - (char_u *)str_arg;
+			}
+		    }
+		    break;
+
+		default:
+		    break;
+		}
+		break;
+
+	    case 'd': case 'u':
+	    case 'b': case 'B':
+	    case 'o':
+	    case 'x': case 'X':
+	    case 'p':
+		{
+		    // NOTE: the u, 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 nonzero
+		    // for unsigned arguments), -1 if negative (unsigned
+		    // argument is never negative)
+		    int arg_sign = 0;
+
+		    // only set for length modifier h, or for no length
+		    // modifiers
+		    int int_arg = 0;
+		    unsigned int uint_arg = 0;
+
+		    // only set for length modifier l
+		    long int long_arg = 0;
+		    unsigned long int ulong_arg = 0;
+
+		    // only set for length modifier ll
+		    varnumber_T llong_arg = 0;
+		    uvarnumber_T ullong_arg = 0;
+
+		    // only set for b conversion
+		    uvarnumber_T bin_arg = 0;
+
+		    // pointer argument value -only defined for p
+		    // conversion
+		    void *ptr_arg = NULL;
+
+		    if (fmt_spec == 'p')
+		    {
+			length_modifier = '\0';
+			ptr_arg =
+# if defined(FEAT_EVAL)
+				 tvs != NULL ? (void *)tv_str(tvs, &arg_idx,
+									NULL) :
+# endif
+					va_arg(ap, void *);
+			if (ptr_arg != NULL)
+			    arg_sign = 1;
+		    }
+		    else if (fmt_spec == 'b' || fmt_spec == 'B')
+		    {
+			bin_arg =
+# if defined(FEAT_EVAL)
+				    tvs != NULL ?
+					   (uvarnumber_T)tv_nr(tvs, &arg_idx) :
+# endif
+					va_arg(ap, uvarnumber_T);
+			if (bin_arg != 0)
+			    arg_sign = 1;
+		    }
+		    else if (fmt_spec == 'd')
+		    {
+			// signed
+			switch (length_modifier)
+			{
+			case '\0':
+			case 'h':
+			    // char and short arguments are passed as int.
+			    int_arg =
+# if defined(FEAT_EVAL)
+					tvs != NULL ? tv_nr(tvs, &arg_idx) :
+# endif
+					    va_arg(ap, int);
+			    if (int_arg > 0)
+				arg_sign =  1;
+			    else if (int_arg < 0)
+				arg_sign = -1;
+			    break;
+			case 'l':
+			    long_arg =
+# if defined(FEAT_EVAL)
+					tvs != NULL ? tv_nr(tvs, &arg_idx) :
+# endif
+					    va_arg(ap, long int);
+			    if (long_arg > 0)
+				arg_sign =  1;
+			    else if (long_arg < 0)
+				arg_sign = -1;
+			    break;
+			case 'L':
+			    llong_arg =
+# if defined(FEAT_EVAL)
+					tvs != NULL ? tv_nr(tvs, &arg_idx) :
+# endif
+					    va_arg(ap, varnumber_T);
+			    if (llong_arg > 0)
+				arg_sign =  1;
+			    else if (llong_arg < 0)
+				arg_sign = -1;
+			    break;
+			}
+		    }
+		    else
+		    {
+			// unsigned
+			switch (length_modifier)
+			{
+			    case '\0':
+			    case 'h':
+				uint_arg =
+# if defined(FEAT_EVAL)
+					    tvs != NULL ? (unsigned)
+							tv_nr(tvs, &arg_idx) :
+# endif
+						va_arg(ap, unsigned int);
+				if (uint_arg != 0)
+				    arg_sign = 1;
+				break;
+			    case 'l':
+				ulong_arg =
+# if defined(FEAT_EVAL)
+					    tvs != NULL ? (unsigned long)
+							tv_nr(tvs, &arg_idx) :
+# endif
+						va_arg(ap, unsigned long int);
+				if (ulong_arg != 0)
+				    arg_sign = 1;
+				break;
+			    case 'L':
+				ullong_arg =
+# if defined(FEAT_EVAL)
+					    tvs != NULL ? (uvarnumber_T)
+							tv_nr(tvs, &arg_idx) :
+# endif
+						va_arg(ap, uvarnumber_T);
+				if (ullong_arg != 0)
+				    arg_sign = 1;
+				break;
+			}
+		    }
+
+		    str_arg = tmp;
+		    str_arg_l = 0;
+
+		    // NOTE:
+		    //   For d, i, u, o, x, and X conversions, if precision is
+		    //   specified, the '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 sprintf to handle, to
+			// avoid handling tricky cases like (short int)-32768
+		    }
+		    else if (alternate_form)
+		    {
+			if (arg_sign != 0
+				     && (fmt_spec == 'b' || fmt_spec == 'B'
+				      || fmt_spec == 'x' || fmt_spec == 'X') )
+			{
+			    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, the resulting formatted string is
+			// empty (d, i, u, b, B, o, x, X, p).
+		    }
+		    else
+		    {
+			char	f[6];
+			int	f_l = 0;
+
+			// construct a simple format string for sprintf
+			f[f_l++] = '%';
+			if (!length_modifier)
+			    ;
+			else if (length_modifier == 'L')
+			{
+# ifdef MSWIN
+			    f[f_l++] = 'I';
+			    f[f_l++] = '6';
+			    f[f_l++] = '4';
+# else
+			    f[f_l++] = 'l';
+			    f[f_l++] = 'l';
+# endif
+			}
+			else
+			    f[f_l++] = length_modifier;
+			f[f_l++] = fmt_spec;
+			f[f_l++] = '\0';
+
+			if (fmt_spec == 'p')
+			    str_arg_l += sprintf(tmp + str_arg_l, f, ptr_arg);
+			else if (fmt_spec == 'b' || fmt_spec == 'B')
+			{
+			    char	    b[8 * sizeof(uvarnumber_T)];
+			    size_t	    b_l = 0;
+			    uvarnumber_T    bn = bin_arg;
+
+			    do
+			    {
+				b[sizeof(b) - ++b_l] = '0' + (bn & 0x1);
+				bn >>= 1;
+			    }
+			    while (bn != 0);
+
+			    memcpy(tmp + str_arg_l, b + sizeof(b) - b_l, b_l);
+			    str_arg_l += b_l;
+			}
+			else if (fmt_spec == 'd')
+			{
+			    // signed
+			    switch (length_modifier)
+			    {
+			    case '\0': str_arg_l += sprintf(
+						 tmp + str_arg_l, f,
+						 int_arg);
+				       break;
+			    case 'h': str_arg_l += sprintf(
+						 tmp + str_arg_l, f,
+						 (short)int_arg);
+				      break;
+			    case 'l': str_arg_l += sprintf(
+						tmp + str_arg_l, f, long_arg);
+				      break;
+			    case 'L': str_arg_l += sprintf(
+					       tmp + str_arg_l, f, llong_arg);
+				      break;
+			    }
+			}
+			else
+			{
+			    // unsigned
+			    switch (length_modifier)
+			    {
+			    case '\0': str_arg_l += sprintf(
+						tmp + str_arg_l, f,
+						uint_arg);
+				       break;
+			    case 'h': str_arg_l += sprintf(
+						tmp + str_arg_l, f,
+						(unsigned short)uint_arg);
+				      break;
+			    case 'l': str_arg_l += sprintf(
+					       tmp + str_arg_l, f, ulong_arg);
+				      break;
+			    case 'L': str_arg_l += sprintf(
+					      tmp + str_arg_l, f, ullong_arg);
+				      break;
+			    }
+			}
+
+			// 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'))
+			    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)
+		    {
+			int n = (int)(min_field_width - (str_arg_l
+						    + number_of_zeros_to_pad));
+			if (n > 0)
+			    number_of_zeros_to_pad += n;
+		    }
+		    break;
+		}
+
+# ifdef FEAT_FLOAT
+	    case 'f':
+	    case 'F':
+	    case 'e':
+	    case 'E':
+	    case 'g':
+	    case 'G':
+		{
+		    // Floating point.
+		    double	f;
+		    double	abs_f;
+		    char	format[40];
+		    int		l;
+		    int		remove_trailing_zeroes = FALSE;
+
+		    f =
+#  if defined(FEAT_EVAL)
+			tvs != NULL ? tv_float(tvs, &arg_idx) :
+#  endif
+			    va_arg(ap, double);
+		    abs_f = f < 0 ? -f : f;
+
+		    if (fmt_spec == 'g' || fmt_spec == 'G')
+		    {
+			// Would be nice to use %g directly, but it prints
+			// "1.0" as "1", we don't want that.
+			if ((abs_f >= 0.001 && abs_f < 10000000.0)
+							      || abs_f == 0.0)
+			    fmt_spec = ASCII_ISUPPER(fmt_spec) ? 'F' : 'f';
+			else
+			    fmt_spec = fmt_spec == 'g' ? 'e' : 'E';
+			remove_trailing_zeroes = TRUE;
+		    }
+
+		    if ((fmt_spec == 'f' || fmt_spec == 'F') &&
+#  ifdef VAX
+			    abs_f > 1.0e38
+#  else
+			    abs_f > 1.0e307
+#  endif
+			    )
+		    {
+			// Avoid a buffer overflow
+			STRCPY(tmp, infinity_str(f > 0.0, fmt_spec,
+					      force_sign, space_for_positive));
+			str_arg_l = STRLEN(tmp);
+			zero_padding = 0;
+		    }
+		    else
+		    {
+			if (isnan(f))
+			{
+			    // Not a number: nan or NAN
+			    STRCPY(tmp, ASCII_ISUPPER(fmt_spec) ? "NAN"
+								      : "nan");
+			    str_arg_l = 3;
+			    zero_padding = 0;
+			}
+			else if (isinf(f))
+			{
+			    STRCPY(tmp, infinity_str(f > 0.0, fmt_spec,
+					      force_sign, space_for_positive));
+			    str_arg_l = STRLEN(tmp);
+			    zero_padding = 0;
+			}
+			else
+			{
+			    // Regular float number
+			    format[0] = '%';
+			    l = 1;
+			    if (force_sign)
+				format[l++] = space_for_positive ? ' ' : '+';
+			    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' || fmt_spec == 'F')
+								&& abs_f > 1.0)
+				    max_prec -= (size_t)log10(abs_f);
+				if (precision > max_prec)
+				    precision = max_prec;
+				l += sprintf(format + l, ".%d", (int)precision);
+			    }
+			    format[l] = fmt_spec == 'F' ? 'f' : fmt_spec;
+			    format[l + 1] = NUL;
+
+			    str_arg_l = sprintf(tmp, format, f);
+			}
+
+			if (remove_trailing_zeroes)
+			{
+			    int i;
+			    char *tp;
+
+			    // Using %g or %G: remove superfluous zeroes.
+			    if (fmt_spec == 'f' || fmt_spec == 'F')
+				tp = tmp + str_arg_l - 1;
+			    else
+			    {
+				tp = (char *)vim_strchr((char_u *)tmp,
+						 fmt_spec == 'e' ? 'e' : 'E');
+				if (tp != NULL)
+				{
+				    // Remove superfluous '+' and leading
+				    // zeroes from the 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
+			{
+			    char *tp;
+
+			    // Be consistent: some printf("%e") use 1.0e+12
+			    // and some 1.0e+012.  Remove one zero in the last
+			    // case.
+			    tp = (char *)vim_strchr((char_u *)tmp,
+						 fmt_spec == 'e' ? 'e' : 'E');
+			    if (tp != NULL && (tp[1] == '+' || tp[1] == '-')
+					  && tp[2] == '0'
+					  && vim_isdigit(tp[3])
+					  && vim_isdigit(tp[4]))
+			    {
+				STRMOVE(tp + 2, tp + 3);
+				--str_arg_l;
+			    }
+			}
+		    }
+		    if (zero_padding && min_field_width > str_arg_l
+					      && (tmp[0] == '-' || force_sign))
+		    {
+			// padding 0's should be inserted after the sign
+			number_of_zeros_to_pad = min_field_width - str_arg_l;
+			zero_padding_insertion_ind = 1;
+		    }
+		    str_arg = tmp;
+		    break;
+		}
+# endif
+
+	    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 != NUL)
+		    str_arg_l++;  // include invalid conversion specifier
+				  // unchanged if not at end-of-string
+		break;
+	    }
+
+	    if (*p != NUL)
+		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)
+	    {
+		// left padding with blank or zero
+		int pn = (int)(min_field_width - (str_arg_l + number_of_zeros_to_pad));
+
+		if (pn > 0)
+		{
+		    if (str_l < str_m)
+		    {
+			size_t avail = str_m - str_l;
+
+			vim_memset(str + str_l, zero_padding ? '0' : ' ',
+					     (size_t)pn > avail ? avail
+								: (size_t)pn);
+		    }
+		    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
+		int zn = (int)zero_padding_insertion_ind;
+
+		if (zn > 0)
+		{
+		    if (str_l < str_m)
+		    {
+			size_t avail = str_m - str_l;
+
+			mch_memmove(str + str_l, str_arg,
+					     (size_t)zn > avail ? avail
+								: (size_t)zn);
+		    }
+		    str_l += zn;
+		}
+
+		// insert zero padding as requested by the precision or min
+		// field width
+		zn = (int)number_of_zeros_to_pad;
+		if (zn > 0)
+		{
+		    if (str_l < str_m)
+		    {
+			size_t avail = str_m - str_l;
+
+			vim_memset(str + str_l, '0',
+					     (size_t)zn > avail ? avail
+								: (size_t)zn);
+		    }
+		    str_l += zn;
+		}
+	    }
+
+	    // insert formatted string
+	    // (or as-is conversion specifier for unknown conversions)
+	    {
+		int sn = (int)(str_arg_l - zero_padding_insertion_ind);
+
+		if (sn > 0)
+		{
+		    if (str_l < str_m)
+		    {
+			size_t avail = str_m - str_l;
+
+			mch_memmove(str + str_l,
+				str_arg + zero_padding_insertion_ind,
+				(size_t)sn > avail ? avail : (size_t)sn);
+		    }
+		    str_l += sn;
+		}
+	    }
+
+	    // insert right padding
+	    if (justify_left)
+	    {
+		// right blank padding to the field width
+		int pn = (int)(min_field_width
+				      - (str_arg_l + number_of_zeros_to_pad));
+
+		if (pn > 0)
+		{
+		    if (str_l < str_m)
+		    {
+			size_t avail = str_m - str_l;
+
+			vim_memset(str + str_l, ' ',
+					     (size_t)pn > avail ? avail
+								: (size_t)pn);
+		    }
+		    str_l += pn;
+		}
+	    }
+	    vim_free(tofree);
+	}
+    }
+
+    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 != NULL && 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;
+}
+
+#endif // PROTO