]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.0.1704: Cannot use positional arguments for printf() v9.0.1704
authorChrist van Willegen <cvwillegen@gmail.com>
Sun, 13 Aug 2023 16:03:14 +0000 (18:03 +0200)
committerChristian Brabandt <cb@256bit.org>
Sun, 13 Aug 2023 16:06:00 +0000 (18:06 +0200)
Problem: Cannot use positional arguments for printf()
Solution: Support positional arguments in string formatting

closes: #12140

Signed-off-by: Christian Brabandt <cb@256bit.org>
Co-authored-by: Christ van Willegen <cvwillegen@gmail.com>
runtime/doc/builtin.txt
src/errors.h
src/globals.h
src/message_test.c
src/po/check.vim
src/strings.c
src/testdir/Make_all.mak
src/testdir/test_expr.vim
src/testdir/test_format.vim [new file with mode: 0644]
src/version.c

index 979f46f2dc79a658dd3c371892e0abc74b12784a..96db898b0375761bc157c776696b1ba240f2fa52 100644 (file)
@@ -6590,7 +6590,11 @@ printf({fmt}, {expr1} ...)                               *printf()*
                The "%" starts a conversion specification.  The following
                arguments appear in sequence:
 
-                       %  [flags]  [field-width]  [.precision]  type
+                       % [pos-argument] [flags] [field-width] [.precision] type
+
+               pos-argument
+                       At most one positional argument specifier. These
+                       take the form {n$}, where n is >= 1.
 
                flags
                        Zero or more of the following flags:
@@ -6662,6 +6666,13 @@ printf({fmt}, {expr1} ...)                               *printf()*
 <              This limits the length of the text used from "line" to
                "width" bytes.
 
+               If the argument to be formatted is specified using a posional
+               argument specifier, and a '*' is used to indicate that a
+               number argument is to be used to specify the width or
+               precision, the argument(s) to be used must also be specified
+               using a {n$} positional argument specifier. See |printf-$|.
+
+
                The conversion specifiers and their meanings are:
 
                                *printf-d* *printf-b* *printf-B* *printf-o*
@@ -6751,6 +6762,103 @@ printf({fmt}, {expr1} ...)                              *printf()*
                of "%" items.  If there are not sufficient or too many
                arguments an error is given.  Up to 18 arguments can be used.
 
+                                                       *printf-$*
+               In certain languages, error and informative messages are
+               more readable when the order of words is different from the
+               corresponding message in English. To accomodate translations
+               having a different word order, positional arguments may be
+               used to indicate this. For instance: >
+
+                       #, c-format
+                       msgid "%s returning %s"
+                       msgstr "waarde %2$s komt terug van %1$s"
+<
+               In this example, the sentence has its 2 string arguments reversed
+               in the output. >
+
+                       echo printf(
+                               "In The Netherlands, vim's creator's name is: %1$s %2$s",
+                               "Bram", "Moolenaar")
+<                      In The Netherlands, vim's creator's name is: Bram Moolenaar >
+
+                       echo printf(
+                               "In Belgium, vim's creator's name is: %2$s %1$s",
+                               "Bram", "Moolenaar")
+<                      In Belgium, vim's creator's name is: Moolenaar Bram
+
+               Width (and precision) can be specified using the '*' specifier.
+               In this case, you must specify the field width position in the
+               argument list. >
+
+                       echo printf("%1$*2$.*3$d", 1, 2, 3)
+<                      001 >
+                       echo printf("%2$*3$.*1$d", 1, 2, 3)
+<                        2 >
+                       echo printf("%3$*1$.*2$d", 1, 2, 3)
+<                      03 >
+                       echo printf("%1$*2$.*3$g", 1.4142, 2, 3)
+<                      1.414
+
+               You can mix specifying the width and/or precision directly
+               and via positional arguments: >
+
+                       echo printf("%1$4.*2$f", 1.4142135, 6)
+<                      1.414214 >
+                       echo printf("%1$*2$.4f", 1.4142135, 6)
+<                      1.4142 >
+                       echo printf("%1$*2$.*3$f", 1.4142135, 6, 2)
+<                        1.41
+
+                                                       *E1400*
+               You cannot mix positional and non-positional arguments: >
+                       echo printf("%s%1$s", "One", "Two")
+<                      E1400: Cannot mix positional and non-positional
+                       arguments: %s%1$s
+
+                                                       *E1401*
+               You cannot skip a positional argument in a format string: >
+                       echo printf("%3$s%1$s", "One", "Two", "Three")
+<                      E1401: format argument 2 unused in $-style
+                       format: %3$s%1$s
+
+                                                       *E1402*
+               You can re-use a [field-width] (or [precision]) argument: >
+                       echo printf("%1$d at width %2$d is: %01$*2$d", 1, 2)
+<                      1 at width 2 is: 01
+
+               However, you can't use it as a different type: >
+                       echo printf("%1$d at width %2$ld is: %01$*2$d", 1, 2)
+<                      E1402: Positional argument 2 used as field
+                       width reused as different type: long int/int
+
+                                                       *E1403*
+               When a positional argument is used, but not the correct number
+               or arguments is given, an error is raised: >
+                       echo printf("%1$d at width %2$d is: %01$*2$.*3$d", 1, 2)
+<                      E1403: Positional argument 3 out of bounds:
+                       %1$d at width %2$d is: %01$*2$.*3$d
+
+               Only the first error is reported: >
+                       echo printf("%01$*2$.*3$d %4$d", 1, 2)
+<                      E1403: Positional argument 3 out of bounds:
+                       %01$*2$.*3$d %4$d
+
+                                                       *E1404*
+               A positional argument can be used more than once: >
+                       echo printf("%1$s %2$s %1$s", "One", "Two")
+<                      One Two One
+
+               However, you can't use a different type the second time: >
+                       echo printf("%1$s %2$s %1$d", "One", "Two")
+<                      E1404: Positional argument 1 type used
+                       inconsistently: int/string
+
+                                                       *E1405*
+               Various other errors that lead to a format string being
+               wrongly formatted lead to: >
+                       echo printf("%1$d at width %2$d is: %01$*2$.3$d", 1, 2)
+<                      E1405: Invalid format specifier:
+                       %1$d at width %2$d is: %01$*2$.3$d
 
 prompt_getprompt({buf})                                        *prompt_getprompt()*
                Returns the effective prompt text for buffer {buf}.  {buf} can
index 9b40cb2549570552e2621c4c6e79ebe9e17e3237..6ef47000a94746d7a9ecbb1350a6a7fd212b00b2 100644 (file)
@@ -3477,3 +3477,17 @@ EXTERN char e_incomplete_type[]
 #endif
 EXTERN char e_warning_pointer_block_corrupted[]
        INIT(= N_("E1364: Warning: Pointer block corrupted"));
+EXTERN char e_cannot_mix_positional_and_non_positional_str[]
+       INIT(= N_("E1400: Cannot mix positional and non-positional arguments: %s"));
+EXTERN char e_fmt_arg_nr_unused_str[]
+       INIT(= N_("E1401: format argument %d unused in $-style format: %s"));
+EXTERN char e_positional_num_field_spec_reused_str_str[]
+       INIT(= N_("E1402: Positional argument %d used as field width reused as different type: %s/%s"));
+EXTERN char e_positional_nr_out_of_bounds_str[]
+       INIT(= N_("E1403: Positional argument %d out of bounds: %s"));
+EXTERN char e_positional_arg_num_type_inconsistent_str_str[]
+       INIT(= N_("E1404: Positional argument %d type used inconsistently: %s/%s"));
+EXTERN char e_invalid_format_specifier_str[]
+       INIT(= N_("E1405: Invalid format specifier: %s"));
+
+// E1365 - E1399 unused
index 69b4343d611af37de3a18756ec2d552764e911a2..6a68c3acbb8f6f56e2a872fa2bad8fa825facf5e 100644 (file)
@@ -1674,6 +1674,19 @@ EXTERN int       cmdwin_result INIT(= 0); // result of cmdline window or 0
 
 EXTERN char_u no_lines_msg[]   INIT(= N_("--No lines in buffer--"));
 
+EXTERN char typename_unknown[] INIT(= N_("unknown"));
+EXTERN char typename_int[]     INIT(= N_("int"));
+EXTERN char typename_longint[] INIT(= N_("long int"));
+EXTERN char typename_longlongint[]     INIT(= N_("long long int"));
+EXTERN char typename_unsignedint[]     INIT(= N_("unsigned int"));
+EXTERN char typename_unsignedlongint[] INIT(= N_("unsigned long int"));
+EXTERN char typename_unsignedlonglongint[]     INIT(= N_("unsigned long long int"));
+EXTERN char typename_pointer[] INIT(= N_("pointer"));
+EXTERN char typename_percent[] INIT(= N_("percent"));
+EXTERN char typename_char[] INIT(= N_("char"));
+EXTERN char typename_string[]  INIT(= N_("string"));
+EXTERN char typename_float[]   INIT(= N_("float"));
+
 /*
  * When ":global" is used to number of substitutions and changed lines is
  * accumulated until it's finished.
index 21ec4db8d3fa553c5f10ecc59968c4cb7f9ee5f4..4ad8041a4617e96b87fe3759fedc7ee49c16b44f 100644 (file)
@@ -39,6 +39,9 @@
 char *fmt_012p = "%012p";
 char *fmt_5S   = "%5S";
 char *fmt_06b  = "%06b";
+char *fmt_06pb = "%1$0.*2$b";
+char *fmt_212s = "%2$s %1$s %2$s";
+char *fmt_21s  = "%2$s %1$s";
 
 /*
  * Test trunc_string().
@@ -181,6 +184,11 @@ test_vim_snprintf(void)
        // buffer and its content should then never be used.
        char *buf = malloc(bsize);
 
+       n = vim_snprintf(buf, bsize, "%.8g", 10000000.1);
+       assert(n == 12);
+       assert(bsize == 0 || STRNCMP(buf, "1.00000001e7", bsize_int) == 0);
+       assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0');
+
        n = vim_snprintf(buf, bsize, "%d", 1234567);
        assert(n == 7);
        assert(bsize == 0 || STRNCMP(buf, "1234567", bsize_int) == 0);
@@ -211,6 +219,12 @@ test_vim_snprintf(void)
        assert(bsize == 0 || STRNCMP(buf, "001100", bsize_int) == 0);
        assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0');
 
+       n = vim_snprintf(buf, bsize, "%s %s", "one", "two");
+       assert(n == 7);
+       assert(bsize == 0 || STRNCMP(buf, "one two", bsize_int) == 0);
+       assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0');
+
+#ifdef FEAT_FLOAT
        n = vim_snprintf(buf, bsize, "%f", 1.234);
        assert(n == 8);
        assert(bsize == 0 || STRNCMP(buf, "1.234000", bsize_int) == 0);
@@ -240,6 +254,7 @@ test_vim_snprintf(void)
        assert(n == 9);
        assert(bsize == 0 || STRNCMP(buf, "-0.000000", bsize_int) == 0);
        assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0');
+#endif
 
        n = vim_snprintf(buf, bsize, "%s", "漢語");
        assert(n == 6);
@@ -304,6 +319,190 @@ test_vim_snprintf(void)
     }
 }
 
+/*
+ * Test vim_snprintf() with a focus on checking that positional
+ * arguments are correctly applied and skipped
+ */
+    static void
+test_vim_snprintf_positional(void)
+{
+    int                n;
+    size_t     bsize;
+    int                bsize_int;
+
+    // Loop on various buffer sizes to make sure that truncation of
+    // vim_snprintf() is correct.
+    for (bsize = 0; bsize < 25; ++bsize)
+    {
+       bsize_int = (int)bsize - 1;
+
+       // buf is the heap rather than in the stack
+       // so valgrind can detect buffer overflows if any.
+       // Use malloc() rather than alloc() as test checks with 0-size
+       // buffer and its content should then never be used.
+       char *buf = malloc(bsize);
+
+       n = vim_snprintf(buf, bsize, "%1$*2$ld", 1234567L, -9);
+       assert(n == 9);
+       assert(bsize == 0 || STRNCMP(buf, "1234567  ", bsize_int) == 0);
+       assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0');
+
+       n = vim_snprintf(buf, bsize, "%1$*2$.*3$ld", 1234567L, -9, 5);
+       assert(n == 9);
+       assert(bsize == 0 || STRNCMP(buf, "1234567  ", bsize_int) == 0);
+       assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0');
+
+       n = vim_snprintf(buf, bsize, "%1$*3$.*2$ld", 1234567L, 5, -9);
+       assert(n == 9);
+       assert(bsize == 0 || STRNCMP(buf, "1234567  ", bsize_int) == 0);
+       assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0');
+
+       n = vim_snprintf(buf, bsize, "%3$*1$.*2$ld", -9, 5, 1234567L);
+       assert(n == 9);
+       assert(bsize == 0 || STRNCMP(buf, "1234567  ", bsize_int) == 0);
+       assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0');
+
+       n = vim_snprintf(buf, bsize, "%1$ld", 1234567L);
+       assert(n == 7);
+       assert(bsize == 0 || STRNCMP(buf, "1234567", bsize_int) == 0);
+       assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0');
+
+       n = vim_snprintf(buf, bsize, "%1$*2$ld", 1234567L, 9);
+       assert(n == 9);
+       assert(bsize == 0 || STRNCMP(buf, "  1234567", bsize_int) == 0);
+       assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0');
+
+       n = vim_snprintf(buf, bsize, "%2$ld %1$d %3$lu", 12345, 9L, 7654321UL);
+       assert(n == 15);
+       assert(bsize == 0 || STRNCMP(buf, "9 12345 7654321", bsize_int) == 0);
+       assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0');
+
+       n = vim_snprintf(buf, bsize, "%2$d %1$ld %3$lu", 1234567L, 9, 7654321UL);
+       assert(n == 17);
+       assert(bsize == 0 || STRNCMP(buf, "9 1234567 7654321", bsize_int) == 0);
+       assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0');
+
+       n = vim_snprintf(buf, bsize, "%2$d %1$lld %3$lu", 1234567LL, 9, 7654321UL);
+       assert(n == 17);
+       assert(bsize == 0 || STRNCMP(buf, "9 1234567 7654321", bsize_int) == 0);
+       assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0');
+
+       n = vim_snprintf(buf, bsize, "%2$ld %1$u %3$lu", 12345U, 9L, 7654321UL);
+       assert(n == 15);
+       assert(bsize == 0 || STRNCMP(buf, "9 12345 7654321", bsize_int) == 0);
+       assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0');
+
+       n = vim_snprintf(buf, bsize, "%2$d %1$lu %3$lu", 1234567UL, 9, 7654321UL);
+       assert(n == 17);
+       assert(bsize == 0 || STRNCMP(buf, "9 1234567 7654321", bsize_int) == 0);
+       assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0');
+
+       n = vim_snprintf(buf, bsize, "%2$d %1$llu %3$lu", 1234567LLU, 9, 7654321UL);
+       assert(n == 17);
+       assert(bsize == 0 || STRNCMP(buf, "9 1234567 7654321", bsize_int) == 0);
+       assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0');
+
+       n = vim_snprintf(buf, bsize, "%2$d %1$llu %3$lu", 1234567LLU, 9, 7654321UL);
+       assert(n == 17);
+       assert(bsize == 0 || STRNCMP(buf, "9 1234567 7654321", bsize_int) == 0);
+       assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0');
+
+       n = vim_snprintf(buf, bsize, "%2$d %1$x %3$lu", 0xdeadbeef, 9, 7654321UL);
+       assert(n == 18);
+       assert(bsize == 0 || STRNCMP(buf, "9 deadbeef 7654321", bsize_int) == 0);
+       assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0');
+
+       n = vim_snprintf(buf, bsize, "%2$ld %1$c %3$lu", 'c', 9L, 7654321UL);
+       assert(n == 11);
+       assert(bsize == 0 || STRNCMP(buf, "9 c 7654321", bsize_int) == 0);
+       assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0');
+
+       n = vim_snprintf(buf, bsize, "%2$ld %1$s %3$lu", "hi", 9L, 7654321UL);
+       assert(n == 12);
+       assert(bsize == 0 || STRNCMP(buf, "9 hi 7654321", bsize_int) == 0);
+       assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0');
+
+       n = vim_snprintf(buf, bsize, "%2$ld %1$e %3$lu", 0.0, 9L, 7654321UL);
+       assert(n == 22);
+       assert(bsize == 0 || STRNCMP(buf, "9 0.000000e+00 7654321", bsize_int) == 0);
+       assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0');
+
+       n = vim_snprintf(buf, bsize, fmt_212s, "one", "two", "three");
+       assert(n == 11);
+       assert(bsize == 0 || STRNCMP(buf, "two one two", bsize_int) == 0);
+       assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0');
+
+       n = vim_snprintf(buf, bsize, "%3$s %1$s %2$s", "one", "two", "three");
+       assert(n == 13);
+       assert(bsize == 0 || STRNCMP(buf, "three one two", bsize_int) == 0);
+       assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0');
+
+       n = vim_snprintf(buf, bsize, "%1$d", 1234567);
+       assert(n == 7);
+       assert(bsize == 0 || STRNCMP(buf, "1234567", bsize_int) == 0);
+       assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0');
+
+       n = vim_snprintf(buf, bsize, "%1$x", 0xdeadbeef);
+       assert(n == 8);
+       assert(bsize == 0 || STRNCMP(buf, "deadbeef", bsize_int) == 0);
+       assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0');
+
+       n = vim_snprintf(buf, bsize, fmt_06pb, (uvarnumber_T)12, 6);
+       assert(n == 6);
+       assert(bsize == 0 || STRNCMP(buf, "001100", bsize_int) == 0);
+       assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0');
+
+       n = vim_snprintf(buf, bsize, "%1$s %2$s", "one", "two");
+       assert(n == 7);
+       assert(bsize == 0 || STRNCMP(buf, "one two", bsize_int) == 0);
+       assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0');
+
+       n = vim_snprintf(buf, bsize, fmt_06b, (uvarnumber_T)12);
+       assert(n == 6);
+       assert(bsize == 0 || STRNCMP(buf, "001100", bsize_int) == 0);
+       assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0');
+
+       n = vim_snprintf(buf, bsize, fmt_21s, "one", "two", "three");
+       assert(n == 7);
+       assert(bsize == 0 || STRNCMP(buf, "two one", bsize_int) == 0);
+       assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0');
+
+#ifdef FEAT_FLOAT
+       n = vim_snprintf(buf, bsize, "%1$f", 1.234);
+       assert(n == 8);
+       assert(bsize == 0 || STRNCMP(buf, "1.234000", bsize_int) == 0);
+       assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0');
+
+       n = vim_snprintf(buf, bsize, "%1$e", 1.234);
+       assert(n == 12);
+       assert(bsize == 0 || STRNCMP(buf, "1.234000e+00", bsize_int) == 0);
+       assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0');
+
+       n = vim_snprintf(buf, bsize, "%1$f", 0.0/0.0);
+       assert(n == 3);
+       assert(bsize == 0 || STRNCMP(buf, "nan", bsize_int) == 0);
+       assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0');
+
+       n = vim_snprintf(buf, bsize, "%1$f", 1.0/0.0);
+       assert(n == 3);
+       assert(bsize == 0 || STRNCMP(buf, "inf", bsize_int) == 0);
+       assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0');
+
+       n = vim_snprintf(buf, bsize, "%1$f", -1.0/0.0);
+       assert(n == 4);
+       assert(bsize == 0 || STRNCMP(buf, "-inf", bsize_int) == 0);
+       assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0');
+
+       n = vim_snprintf(buf, bsize, "%1$f", -0.0);
+       assert(n == 9);
+       assert(bsize == 0 || STRNCMP(buf, "-0.000000", bsize_int) == 0);
+       assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0');
+#endif
+
+       free(buf);
+    }
+}
+
     int
 main(int argc, char **argv)
 {
@@ -317,11 +516,13 @@ main(int argc, char **argv)
     test_trunc_string();
     test_trunc_string_mbyte();
     test_vim_snprintf();
+    test_vim_snprintf_positional();
 
     set_option_value_give_err((char_u *)"encoding", 0, (char_u *)"latin1", 0);
     init_chartab();
     test_trunc_string();
     test_vim_snprintf();
+    test_vim_snprintf_positional();
 
     return 0;
 }
index b2a5fd582eb7fd7b0baf404abb4d6d9a13ae4fe0..917c648494376a908dc3f9fae64d75fa7196bf79 100644 (file)
@@ -30,8 +30,15 @@ func! GetMline()
   " remove '%' used for plural forms.
   let idline = substitute(idline, '\\nPlural-Forms: .\+;\\n', '', '')
 
+  " remove duplicate positional format arguments
+  let idline2 = ""
+  while idline2 != idline
+    let idline2 = idline
+    let idline = substitute(idline, '%\([1-9][0-9]*\)\$\([-+ #''.*]*[0-9]*l\=[dsuxXpoc%]\)\(.*\)%\1$\([-+ #''.*]*\)\(l\=[dsuxXpoc%]\)', '%\1$\2\3\4', 'g')
+  endwhile
+
   " remove everything but % items.
-  return substitute(idline, '[^%]*\(%[-+ #''.0-9*]*l\=[dsuxXpoc%]\)\=', '\1', 'g')
+  return substitute(idline, '[^%]*\(%([1-9][0-9]*\$)\=[-+ #''.0-9*]*l\=[dsuxXpoc%]\)\=', '\1', 'g')
 endfunc
 
 " This only works when 'wrapscan' is not set.
index e3be3354d39ee06f7b95009d09fac00896eba139..ead59164e49d02873e4813112ba894fe4bb248b2 100644 (file)
@@ -2242,17 +2242,665 @@ vim_vsnprintf(
     return vim_vsnprintf_typval(str, str_m, fmt, ap, NULL);
 }
 
+enum
+{
+    TYPE_UNKNOWN = -1,
+    TYPE_INT,
+    TYPE_LONGINT,
+    TYPE_LONGLONGINT,
+    TYPE_UNSIGNEDINT,
+    TYPE_UNSIGNEDLONGINT,
+    TYPE_UNSIGNEDLONGLONGINT,
+    TYPE_POINTER,
+    TYPE_PERCENT,
+    TYPE_CHAR,
+    TYPE_STRING,
+    TYPE_FLOAT
+};
+
+/* Types that can be used in a format string
+ */
+    int
+format_typeof(
+    const char *type,
+    int                usetvs UNUSED)
+{
+    // allowed values: \0, h, l, L
+    char    length_modifier = '\0';
+
+    // current conversion specifier character
+    char    fmt_spec = '\0';
+
+    // parse 'h', 'l' and 'll' length modifiers
+    if (*type == 'h' || *type == 'l')
+    {
+       length_modifier = *type;
+       type++;
+       if (length_modifier == 'l' && *type == 'l')
+       {
+           // double l = __int64 / varnumber_T
+           length_modifier = 'L';
+           type++;
+       }
+    }
+    fmt_spec = *type;
+
+    // common synonyms:
+    switch (fmt_spec)
+    {
+       case 'i': fmt_spec = 'd'; break;
+       case '*': fmt_spec = 'd'; length_modifier = 'h'; 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)
+    if (usetvs)
+    {
+       switch (fmt_spec)
+       {
+           case 'd': case 'u': case 'o': case 'x': case 'X':
+               if (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 '%':
+       return TYPE_PERCENT;
+
+    case 'c':
+       return TYPE_CHAR;
+
+    case 's':
+    case 'S':
+       return TYPE_STRING;
+
+    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)
+
+           if (fmt_spec == 'p')
+               return TYPE_POINTER;
+           else if (fmt_spec == 'b' || fmt_spec == 'B')
+               return TYPE_UNSIGNEDINT;
+           else if (fmt_spec == 'd')
+           {
+               // signed
+               switch (length_modifier)
+               {
+               case '\0':
+               case 'h':
+                   // char and short arguments are passed as int.
+                   return TYPE_INT;
+               case 'l':
+                   return TYPE_LONGINT;
+               case 'L':
+                   return TYPE_LONGLONGINT;
+               }
+           }
+           else
+           {
+               // unsigned
+               switch (length_modifier)
+               {
+                   case '\0':
+                   case 'h':
+                       return TYPE_UNSIGNEDINT;
+                   case 'l':
+                       return TYPE_UNSIGNEDLONGINT;
+                   case 'L':
+                       return TYPE_UNSIGNEDLONGLONGINT;
+               }
+           }
+       }
+       break;
+
+    case 'f':
+    case 'F':
+    case 'e':
+    case 'E':
+    case 'g':
+    case 'G':
+       return TYPE_FLOAT;
+    }
+
+    return TYPE_UNKNOWN;
+}
+
+    char *
+format_typename(
+    const char  *type)
+{
+    switch (format_typeof(type, FALSE))
+    {
+       case TYPE_INT:
+           return _(typename_int);
+
+       case TYPE_LONGINT:
+           return _(typename_longint);
+
+       case TYPE_LONGLONGINT:
+           return _(typename_longlongint);
+
+       case TYPE_UNSIGNEDINT:
+           return _(typename_unsignedint);
+
+       case TYPE_UNSIGNEDLONGINT:
+           return _(typename_unsignedlongint);
+
+       case TYPE_UNSIGNEDLONGLONGINT:
+           return _(typename_unsignedlonglongint);
+
+       case TYPE_POINTER:
+           return _(typename_pointer);
+
+       case TYPE_PERCENT:
+           return _(typename_percent);
+
+       case TYPE_CHAR:
+           return _(typename_char);
+
+       case TYPE_STRING:
+           return _(typename_string);
+
+       case TYPE_FLOAT:
+           return _(typename_float);
+    }
+
+    return _(typename_unknown);
+}
+
+    int
+adjust_types(
+    const char ***ap_types,
+    int arg,
+    int *num_posarg,
+    const char *type)
+{
+    if (*ap_types == NULL || *num_posarg < arg)
+    {
+       int         idx;
+       const char  **new_types;
+
+       if (*ap_types == NULL)
+           new_types = ALLOC_CLEAR_MULT(const char *, arg);
+       else
+           new_types = vim_realloc(*ap_types, arg * sizeof(const char *));
+
+       if (new_types == NULL)
+           return FAIL;
+
+       for (idx = *num_posarg; idx < arg; ++idx)
+           new_types[idx] = NULL;
+
+       *ap_types = new_types;
+       *num_posarg = arg;
+    }
+
+    if ((*ap_types)[arg - 1] != NULL)
+    {
+       if ((*ap_types)[arg - 1][0] == '*' || type[0] == '*')
+       {
+           const char *pt = type;
+           if (pt[0] == '*')
+               pt = (*ap_types)[arg - 1];
+
+           if (pt[0] != '*')
+           {
+               switch (pt[0])
+               {
+                   case 'd': case 'i': break;
+                   default:
+                       semsg(_(e_positional_num_field_spec_reused_str_str), arg, format_typename((*ap_types)[arg - 1]), format_typename(type));
+                       return FAIL;
+               }
+           }
+       }
+       else
+       {
+           if (format_typeof(type, FALSE) != format_typeof((*ap_types)[arg - 1], FALSE))
+           {
+               semsg(_( e_positional_arg_num_type_inconsistent_str_str), arg, format_typename(type), format_typename((*ap_types)[arg - 1]));
+               return FAIL;
+           }
+       }
+    }
+
+    (*ap_types)[arg - 1] = type;
+
+    return OK;
+}
+
+    int
+parse_fmt_types(
+    const char  ***ap_types,
+    int                *num_posarg,
+    const char  *fmt,
+    typval_T   *tvs UNUSED
+    )
+{
+    const char *p = fmt;
+    const char *arg = NULL;
+
+    int                any_pos = 0;
+    int                any_arg = 0;
+    int                arg_idx;
+
+#define CHECK_POS_ARG do { \
+    if (any_pos && any_arg) \
+    { \
+       semsg(_( e_cannot_mix_positional_and_non_positional_str), fmt); \
+       goto error; \
+    } \
+} while (0);
+
+    if (p == NULL)
+       return OK;
+
+    while (*p != NUL)
+    {
+       if (*p != '%')
+       {
+           char    *q = strchr(p + 1, '%');
+           size_t  n = (q == NULL) ? STRLEN(p) : (size_t)(q - p);
+
+           p += n;
+       }
+       else
+       {
+           // allowed values: \0, h, l, L
+           char        length_modifier = '\0';
+
+           // variable for positional arg
+           int         pos_arg = -1;
+           const char  *ptype = NULL;
+
+           p++;  // skip '%'
+
+           // First check to see if we find a positional
+           // argument specifier
+           ptype = p;
+
+           while (VIM_ISDIGIT(*ptype))
+               ++ptype;
+
+           if (*ptype == '$')
+           {
+               if (*p == '0')
+               {
+                   // 0 flag at the wrong place
+                   semsg(_( e_invalid_format_specifier_str), fmt);
+                   goto error;
+               }
+
+               // Positional argument
+               unsigned int uj = *p++ - '0';
+
+               while (VIM_ISDIGIT((int)(*p)))
+                   uj = 10 * uj + (unsigned int)(*p++ - '0');
+               pos_arg = uj;
+
+               any_pos = 1;
+               CHECK_POS_ARG;
+
+               ++p;
+           }
+
+           // parse flags
+           while (*p == '0' || *p == '-' || *p == '+' || *p == ' '
+                                                  || *p == '#' || *p == '\'')
+           {
+               switch (*p)
+               {
+                   case '0': break;
+                   case '-': break;
+                   case '+': break;
+                   case ' ': // If both the ' ' and '+' flags appear, the ' '
+                             // flag should be ignored
+                             break;
+                   case '#': break;
+                   case '\'': break;
+               }
+               p++;
+           }
+           // If the '0' and '-' flags both appear, the '0' flag should be
+           // ignored.
+
+           // parse field width
+           if (*(arg = p) == '*')
+           {
+               p++;
+
+               if (VIM_ISDIGIT((int)(*p)))
+               {
+                   // Positional argument field width
+                   unsigned int uj = *p++ - '0';
+
+                   while (VIM_ISDIGIT((int)(*p)))
+                       uj = 10 * uj + (unsigned int)(*p++ - '0');
+
+                   if (*p != '$')
+                   {
+                       semsg(_( e_invalid_format_specifier_str), fmt);
+                       goto error;
+                   }
+                   else
+                   {
+                       ++p;
+                       any_pos = 1;
+                       CHECK_POS_ARG;
+
+                       if (adjust_types(ap_types, uj, num_posarg, arg) == FAIL)
+                           goto error;
+                   }
+               }
+               else
+               {
+                   any_arg = 1;
+                   CHECK_POS_ARG;
+               }
+           }
+           else if (VIM_ISDIGIT((int)(*(arg = 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');
+
+               if (*p == '$')
+               {
+                   semsg(_( e_invalid_format_specifier_str), fmt);
+                   goto error;
+               }
+           }
+
+           // parse precision
+           if (*p == '.')
+           {
+               p++;
+
+               if (*(arg = p) == '*')
+               {
+                   p++;
+
+                   if (VIM_ISDIGIT((int)(*p)))
+                   {
+                       // Parse precision
+                       unsigned int uj = *p++ - '0';
+
+                       while (VIM_ISDIGIT((int)(*p)))
+                           uj = 10 * uj + (unsigned int)(*p++ - '0');
+
+                       if (*p == '$')
+                       {
+                           any_pos = 1;
+                           CHECK_POS_ARG;
+
+                           ++p;
+
+                           if (adjust_types(ap_types, uj, num_posarg, arg) == FAIL)
+                               goto error;
+                       }
+                       else
+                       {
+                           semsg(_( e_invalid_format_specifier_str), fmt);
+                           goto error;
+                       }
+                   }
+                   else
+                   {
+                       any_arg = 1;
+                       CHECK_POS_ARG;
+                   }
+               }
+               else if (VIM_ISDIGIT((int)(*(arg = 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');
+
+                   if (*p == '$')
+                   {
+                       semsg(_( e_invalid_format_specifier_str), fmt);
+                       goto error;
+                   }
+               }
+           }
+
+           if (pos_arg != -1)
+           {
+               any_pos = 1;
+               CHECK_POS_ARG;
+
+               ptype = p;
+           }
+
+           // 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++;
+               }
+           }
+
+           switch (*p)
+           {
+               // Check for known format specifiers. % is special!
+               case 'i':
+               case '*':
+               case 'd':
+               case 'u':
+               case 'o':
+               case 'D':
+               case 'U':
+               case 'O':
+               case 'x':
+               case 'X':
+               case 'b':
+               case 'B':
+               case 'c':
+               case 's':
+               case 'S':
+               case 'p':
+               case 'f':
+               case 'F':
+               case 'e':
+               case 'E':
+               case 'g':
+               case 'G':
+                   if (pos_arg != -1)
+                   {
+                       if (adjust_types(ap_types, pos_arg, num_posarg, ptype) == FAIL)
+                           goto error;
+                   }
+                   else
+                   {
+                       any_arg = 1;
+                       CHECK_POS_ARG;
+                   }
+                   break;
+
+               default:
+                   if (pos_arg != -1)
+                   {
+                       semsg(_( e_cannot_mix_positional_and_non_positional_str), fmt);
+                       goto error;
+                   }
+           }
+
+           if (*p != NUL)
+               p++;     // step over the just processed conversion specifier
+       }
+    }
+
+    for (arg_idx = 0; arg_idx < *num_posarg; ++arg_idx)
+    {
+       if ((*ap_types)[arg_idx] == NULL)
+       {
+           semsg(_(e_fmt_arg_nr_unused_str), arg_idx + 1, fmt);
+           goto error;
+       }
+
+# if defined(FEAT_EVAL)
+       if (tvs != NULL && tvs[arg_idx].v_type == VAR_UNKNOWN)
+       {
+           semsg(_(e_positional_nr_out_of_bounds_str), arg_idx + 1, fmt);
+           goto error;
+       }
+# endif
+    }
+
+    return OK;
+
+error:
+    vim_free(*ap_types);
+    *ap_types = NULL;
+    *num_posarg = 0;
+    return FAIL;
+}
+
+    void
+skip_to_arg(
+    const char **ap_types,
+    va_list    ap_start,
+    va_list    *ap,
+    int                *arg_idx,
+    int                *arg_cur)
+{
+    int                arg_min = 0;
+
+    if (*arg_cur + 1 == *arg_idx)
+    {
+       ++*arg_cur;
+       ++*arg_idx;
+       return;
+    }
+
+    if (*arg_cur >= *arg_idx)
+    {
+       // Reset ap to ap_start and skip arg_idx - 1 types
+       va_end(*ap);
+       va_copy(*ap, ap_start);
+    }
+    else
+    {
+       // Skip over any we should skip
+       arg_min = *arg_cur;
+    }
+
+    for (*arg_cur = arg_min; *arg_cur < *arg_idx - 1; ++*arg_cur)
+    {
+       const char *p = ap_types[*arg_cur];
+
+       int fmt_type = format_typeof(p, TRUE);
+
+       // get parameter value, do initial processing
+       switch (fmt_type)
+       {
+       case TYPE_PERCENT:
+       case TYPE_UNKNOWN:
+           break;
+
+       case TYPE_CHAR:
+           va_arg(*ap, int);
+           break;
+
+       case TYPE_STRING:
+           va_arg(*ap, char *);
+           break;
+
+       case TYPE_POINTER:
+           va_arg(*ap, void *);
+           break;
+
+       case TYPE_INT:
+           va_arg(*ap, int);
+           break;
+
+       case TYPE_LONGINT:
+           va_arg(*ap, long int);
+           break;
+
+       case TYPE_LONGLONGINT:
+           va_arg(*ap, varnumber_T);
+           break;
+
+       case TYPE_UNSIGNEDINT:
+           va_arg(*ap, unsigned int);
+           break;
+
+       case TYPE_UNSIGNEDLONGINT:
+           va_arg(*ap, unsigned long int);
+           break;
+
+       case TYPE_UNSIGNEDLONGLONGINT:
+           va_arg(*ap, uvarnumber_T);
+           break;
+
+       case TYPE_FLOAT:
+           va_arg(*ap, double);
+           break;
+       }
+    }
+
+    // Because we know that after we return from this call,
+    // a va_arg() call is made, we can pre-emptively
+    // increment the current argument index.
+    ++*arg_cur;
+    ++*arg_idx;
+
+    return;
+}
+
     int
 vim_vsnprintf_typval(
     char       *str,
     size_t     str_m,
     const char *fmt,
-    va_list    ap,
+    va_list    ap_start,
     typval_T   *tvs)
 {
     size_t     str_l = 0;
     const char *p = fmt;
+    int                arg_cur = 0;
+    int                num_posarg = 0;
     int                arg_idx = 1;
+    va_list    ap;
+    const char **ap_types = NULL;
+
+    if (parse_fmt_types(&ap_types, &num_posarg, fmt, tvs) == FAIL)
+       return 0;
+
+    va_copy(ap, ap_start);
 
     if (p == NULL)
        p = "";
@@ -2316,9 +2964,32 @@ vim_vsnprintf_typval(
            // buffer for 's' and 'S' specs
            char_u  *tofree = NULL;
 
+           // variables for positional arg
+           int     pos_arg = -1;
+           const char  *ptype;
+
 
            p++;  // skip '%'
 
+           // First check to see if we find a positional
+           // argument specifier
+           ptype = p;
+
+           while (VIM_ISDIGIT(*ptype))
+               ++ptype;
+
+           if (*ptype == '$')
+           {
+               // Positional argument
+               unsigned int uj = *p++ - '0';
+
+               while (VIM_ISDIGIT((int)(*p)))
+                   uj = 10 * uj + (unsigned int)(*p++ - '0');
+               pos_arg = uj;
+
+               ++p;
+           }
+
            // parse flags
            while (*p == '0' || *p == '-' || *p == '+' || *p == ' '
                                                   || *p == '#' || *p == '\'')
@@ -2346,11 +3017,26 @@ vim_vsnprintf_typval(
                int j;
 
                p++;
+
+               if (VIM_ISDIGIT((int)(*p)))
+               {
+                   // Positional argument field width
+                   unsigned int uj = *p++ - '0';
+
+                   while (VIM_ISDIGIT((int)(*p)))
+                       uj = 10 * uj + (unsigned int)(*p++ - '0');
+                   arg_idx = uj;
+
+                   ++p;
+               }
+
                j =
 # if defined(FEAT_EVAL)
                    tvs != NULL ? tv_nr(tvs, &arg_idx) :
 # endif
-                       va_arg(ap, int);
+                       (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur),
+                       va_arg(ap, int));
+
                if (j >= 0)
                    min_field_width = j;
                else
@@ -2375,16 +3061,42 @@ vim_vsnprintf_typval(
            {
                p++;
                precision_specified = 1;
-               if (*p == '*')
+
+               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;
+               }
+               else if (*p == '*')
                {
                    int j;
 
+                   p++;
+
+                   if (VIM_ISDIGIT((int)(*p)))
+                   {
+                       // positional argument
+                       unsigned int uj = *p++ - '0';
+
+                       while (VIM_ISDIGIT((int)(*p)))
+                           uj = 10 * uj + (unsigned int)(*p++ - '0');
+                       arg_idx = uj;
+
+                       ++p;
+                   }
+
                    j =
 # if defined(FEAT_EVAL)
                        tvs != NULL ? tv_nr(tvs, &arg_idx) :
 # endif
-                           va_arg(ap, int);
-                   p++;
+                           (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur),
+                           va_arg(ap, int));
+
                    if (j >= 0)
                        precision = j;
                    else
@@ -2393,16 +3105,6 @@ vim_vsnprintf_typval(
                        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
@@ -2438,6 +3140,9 @@ vim_vsnprintf_typval(
            }
 # endif
 
+           if (pos_arg != -1)
+               arg_idx = pos_arg;
+
            // get parameter value, do initial processing
            switch (fmt_spec)
            {
@@ -2462,7 +3167,9 @@ vim_vsnprintf_typval(
 # if defined(FEAT_EVAL)
                            tvs != NULL ? tv_nr(tvs, &arg_idx) :
 # endif
-                               va_arg(ap, int);
+                               (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur),
+                               va_arg(ap, int));
+
                        // standard demands unsigned char
                        uchar_arg = (unsigned char)j;
                        str_arg = (char *)&uchar_arg;
@@ -2475,7 +3182,9 @@ vim_vsnprintf_typval(
 # if defined(FEAT_EVAL)
                                tvs != NULL ? tv_str(tvs, &arg_idx, &tofree) :
 # endif
-                                   va_arg(ap, char *);
+                                   (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur),
+                                   va_arg(ap, char *));
+
                    if (str_arg == NULL)
                    {
                        str_arg = "[NULL]";
@@ -2570,7 +3279,9 @@ vim_vsnprintf_typval(
                                 tvs != NULL ? (void *)tv_str(tvs, &arg_idx,
                                                                        NULL) :
 # endif
-                                       va_arg(ap, void *);
+                                       (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur),
+                                       va_arg(ap, void *));
+
                        if (ptr_arg != NULL)
                            arg_sign = 1;
                    }
@@ -2581,7 +3292,9 @@ vim_vsnprintf_typval(
                                    tvs != NULL ?
                                           (uvarnumber_T)tv_nr(tvs, &arg_idx) :
 # endif
-                                       va_arg(ap, uvarnumber_T);
+                                       (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur),
+                                       va_arg(ap, uvarnumber_T));
+
                        if (bin_arg != 0)
                            arg_sign = 1;
                    }
@@ -2597,7 +3310,9 @@ vim_vsnprintf_typval(
 # if defined(FEAT_EVAL)
                                        tvs != NULL ? tv_nr(tvs, &arg_idx) :
 # endif
-                                           va_arg(ap, int);
+                                           (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur),
+                                           va_arg(ap, int));
+
                            if (int_arg > 0)
                                arg_sign =  1;
                            else if (int_arg < 0)
@@ -2608,7 +3323,9 @@ vim_vsnprintf_typval(
 # if defined(FEAT_EVAL)
                                        tvs != NULL ? tv_nr(tvs, &arg_idx) :
 # endif
-                                           va_arg(ap, long int);
+                                           (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur),
+                                           va_arg(ap, long int));
+
                            if (long_arg > 0)
                                arg_sign =  1;
                            else if (long_arg < 0)
@@ -2619,7 +3336,9 @@ vim_vsnprintf_typval(
 # if defined(FEAT_EVAL)
                                        tvs != NULL ? tv_nr(tvs, &arg_idx) :
 # endif
-                                           va_arg(ap, varnumber_T);
+                                           (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur),
+                                           va_arg(ap, varnumber_T));
+
                            if (llong_arg > 0)
                                arg_sign =  1;
                            else if (llong_arg < 0)
@@ -2639,7 +3358,9 @@ vim_vsnprintf_typval(
                                            tvs != NULL ? (unsigned)
                                                        tv_nr(tvs, &arg_idx) :
 # endif
-                                               va_arg(ap, unsigned int);
+                                               (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur),
+                                               va_arg(ap, unsigned int));
+
                                if (uint_arg != 0)
                                    arg_sign = 1;
                                break;
@@ -2649,7 +3370,9 @@ vim_vsnprintf_typval(
                                            tvs != NULL ? (unsigned long)
                                                        tv_nr(tvs, &arg_idx) :
 # endif
-                                               va_arg(ap, unsigned long int);
+                                               (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur),
+                                               va_arg(ap, unsigned long int));
+
                                if (ulong_arg != 0)
                                    arg_sign = 1;
                                break;
@@ -2659,7 +3382,9 @@ vim_vsnprintf_typval(
                                            tvs != NULL ? (uvarnumber_T)
                                                        tv_nr(tvs, &arg_idx) :
 # endif
-                                               va_arg(ap, uvarnumber_T);
+                                               (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur),
+                                               va_arg(ap, uvarnumber_T));
+
                                if (ullong_arg != 0)
                                    arg_sign = 1;
                                break;
@@ -2859,7 +3584,9 @@ vim_vsnprintf_typval(
 # if defined(FEAT_EVAL)
                        tvs != NULL ? tv_float(tvs, &arg_idx) :
 # endif
-                           va_arg(ap, double);
+                           (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur),
+                           va_arg(ap, double));
+
                    abs_f = f < 0 ? -f : f;
 
                    if (fmt_spec == 'g' || fmt_spec == 'G')
@@ -3143,9 +3870,12 @@ vim_vsnprintf_typval(
        str[str_l <= str_m - 1 ? str_l : str_m - 1] = '\0';
     }
 
-    if (tvs != NULL && tvs[arg_idx - 1].v_type != VAR_UNKNOWN)
+    if (tvs != NULL && tvs[num_posarg != 0 ? num_posarg : arg_idx - 1].v_type != VAR_UNKNOWN)
        emsg(_(e_too_many_arguments_to_printf));
 
+    vim_free(ap_types);
+    va_end(ap);
+
     // 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.
index f1982171696ffa5fecf2f744f63c8ce9d61c7ab5..fbdc3c7ee8eafef1d312e269931b1d4eb444738b 100644 (file)
@@ -150,6 +150,7 @@ NEW_TESTS = \
        test_fnameescape \
        test_fnamemodify \
        test_fold \
+       test_format \
        test_functions \
        test_function_lists \
        test_ga \
index f7f6e653212549ae9d265fe744814db6c497de62..40b7809d1536144d7409ec3e3c797347720b0dbe 100644 (file)
@@ -291,6 +291,8 @@ func Test_printf_misc()
   let lines =<< trim END
       call assert_equal('123', printf('123'))
 
+      call assert_equal('', printf('%'))
+      call assert_equal('', printf('%.0d', 0))
       call assert_equal('123', printf('%d', 123))
       call assert_equal('123', printf('%i', 123))
       call assert_equal('123', printf('%D', 123))
diff --git a/src/testdir/test_format.vim b/src/testdir/test_format.vim
new file mode 100644 (file)
index 0000000..f665d15
--- /dev/null
@@ -0,0 +1,361 @@
+" Tests for expressions.
+
+source check.vim
+import './vim9.vim' as v9
+
+func Test_printf_pos_misc()
+  let lines =<< trim END
+      call assert_equal('123', printf('%1$d', 123))
+      call assert_equal('', printf('%1$.0d', 0))
+      call assert_equal('00005', printf('%1$5.5d', 5))
+      call assert_equal('00005', printf('%1$*1$.5d', 5))
+      call assert_equal('00005', printf('%1$5.*1$d', 5))
+      call assert_equal('00005', printf('%1$*1$.*1$d', 5))
+      call assert_equal('00005', printf('%1$*10$.5d%2$.0d%3$.0d%4$.0d%5$.0d%6$.0d%7$.0d%8$.0d%9$.0d', 5, 0, 0, 0, 0, 0, 0, 0, 0, 5))
+      call assert_equal('00005', printf('%1$5.*10$d%2$.0d%3$.0d%4$.0d%5$.0d%6$.0d%7$.0d%8$.0d%9$.0d', 5, 0, 0, 0, 0, 0, 0, 0, 0, 5))
+      call assert_equal('123', printf('%1$i', 123))
+      call assert_equal('123', printf('%1$D', 123))
+      call assert_equal('123', printf('%1$U', 123))
+      call assert_equal('173', printf('%1$o', 123))
+      call assert_equal('173', printf('%1$O', 123))
+      call assert_equal('7b', printf('%1$x', 123))
+      call assert_equal('7B', printf('%1$X', 123))
+      call assert_equal('Printing 1 at width 1 gives: 1', 1->printf("Printing %1$d at width %1$d gives: %1$*1$d"))
+      call assert_equal('Printing 2 at width 2 gives:  2', 2->printf("Printing %1$d at width %1$d gives: %1$*1$d"))
+      call assert_equal('Printing 3 at width 3 gives:   3', 3->printf("Printing %1$d at width %1$d gives: %1$*1$d"))
+      call assert_equal('Printing 1 at width/precision 1.1 gives: 1', 1->printf("Printing %1$d at width/precision %1$d.%1$d gives: %1$*1$.*1$d"))
+      call assert_equal('Printing 2 at width/precision 2.2 gives: 02', 2->printf("Printing %1$d at width/precision %1$d.%1$d gives: %1$*1$.*1$d"))
+      call assert_equal('Printing 3 at width/precision 3.3 gives: 003', 3->printf("Printing %1$d at width/precision %1$d.%1$d gives: %1$*1$.*1$d"))
+
+      call assert_equal('123', printf('%1$hd', 123))
+      call assert_equal('-123', printf('%1$hd', -123))
+      call assert_equal('-1', printf('%1$hd', 0xFFFF))
+      call assert_equal('-1', printf('%1$hd', 0x1FFFFF))
+
+      call assert_equal('123', printf('%1$hu', 123))
+      call assert_equal('65413', printf('%1$hu', -123))
+      call assert_equal('65535', printf('%1$hu', 0xFFFF))
+      call assert_equal('65535', printf('%1$hu', 0x1FFFFF))
+
+      call assert_equal('123', printf('%1$ld', 123))
+      call assert_equal('-123', printf('%1$ld', -123))
+      call assert_equal('65535', printf('%1$ld', 0xFFFF))
+      call assert_equal('131071', printf('%1$ld', 0x1FFFF))
+
+      call assert_equal('{', printf('%1$c', 123))
+      call assert_equal('abc', printf('%1$s', 'abc'))
+      call assert_equal('abc', printf('%1$S', 'abc'))
+
+      call assert_equal('+123', printf('%1$+d', 123))
+      call assert_equal('-123', printf('%1$+d', -123))
+      call assert_equal('+123', printf('%1$+ d', 123))
+      call assert_equal(' 123', printf('%1$ d', 123))
+      call assert_equal(' 123', printf('%1$  d', 123))
+      call assert_equal('-123', printf('%1$ d', -123))
+
+      call assert_equal('  123', printf('%2$*1$d', 5, 123))
+      call assert_equal('123  ', printf('%2$*1$d', -5, 123))
+      call assert_equal('00123', printf('%2$.*1$d', 5, 123))
+      call assert_equal('  123', printf('%2$ *1$d', 5, 123))
+      call assert_equal(' +123', printf('%2$+ *1$d', 5, 123))
+
+      call assert_equal('  123', printf('%1$*2$d', 123, 5))
+      call assert_equal('123  ', printf('%1$*2$d', 123, -5))
+      call assert_equal('00123', printf('%1$.*2$d', 123, 5))
+      call assert_equal('  123', printf('%1$ *2$d', 123, 5))
+      call assert_equal(' +123', printf('%1$+ *2$d', 123, 5))
+
+      call assert_equal('foobar', printf('%2$.*1$s',  9, 'foobar'))
+      call assert_equal('foo',    printf('%2$.*1$s',  3, 'foobar'))
+      call assert_equal('',       printf('%2$.*1$s',  0, 'foobar'))
+      call assert_equal('foobar', printf('%2$.*1$s', -1, 'foobar'))
+
+      #" Unrecognized format specifier kept as-is.
+      call assert_equal('_123', printf("%_%1$d", 123))
+
+      #" Test alternate forms.
+      call assert_equal('0x7b', printf('%1$#x', 123))
+      call assert_equal('0X7B', printf('%1$#X', 123))
+      call assert_equal('0173', printf('%1$#o', 123))
+      call assert_equal('0173', printf('%1$#O', 123))
+      call assert_equal('abc', printf('%1$#s', 'abc'))
+      call assert_equal('abc', printf('%1$#S', 'abc'))
+
+      call assert_equal('1%', printf('%1$d%%', 1))
+      call assert_notequal('', printf('%1$p', "abc"))
+      call assert_notequal('', printf('%2$d %1$p %3$s', "abc", 2, "abc"))
+
+      #" Try argument re-use and argument swapping
+      call assert_equal('one two one', printf('%1$s %2$s %1$s', "one", "two"))
+      call assert_equal('Screen height: 400', printf('%1$s height: %2$d', "Screen", 400))
+      call assert_equal('400 is: Screen height', printf('%2$d is: %1$s height', "Screen", 400))
+
+      #" Try out lots of combinations of argument types to skip
+      call assert_equal('9 12345 7654321', printf('%2$ld %1$d %3$lu', 12345, 9, 7654321))
+      call assert_equal('9 1234567 7654321', printf('%2$d %1$ld %3$lu', 1234567, 9, 7654321))
+      call assert_equal('9 1234567 7654321', printf('%2$d %1$lld %3$lu', 1234567, 9, 7654321))
+      call assert_equal('9 12345 7654321', printf('%2$ld %1$u %3$lu', 12345, 9, 7654321))
+      call assert_equal('9 1234567 7654321', printf('%2$d %1$lu %3$lu', 1234567, 9, 7654321))
+      call assert_equal('9 1234567 7654321', printf('%2$d %1$llu %3$lu', 1234567, 9, 7654321))
+      call assert_equal('9 1234567 7654321', printf('%2$d %1$llu %3$lu', 1234567, 9, 7654321))
+      call assert_equal('9 deadbeef 7654321', printf('%2$d %1$x %3$lu', 0xdeadbeef, 9, 7654321))
+      call assert_equal('9 c 7654321', printf('%2$ld %1$c %3$lu', 99, 9, 7654321))
+      call assert_equal('9 hi 7654321', printf('%2$ld %1$s %3$lu', "hi", 9, 7654321))
+      call assert_equal('9 0.000000e+00 7654321', printf('%2$ld %1$e %3$lu', 0.0, 9, 7654321))
+  END
+  call v9.CheckLegacyAndVim9Success(lines)
+
+  call v9.CheckLegacyAndVim9Failure(["call printf('%1$d%2$d', 1, 3, 4)"], "E767:")
+
+  call v9.CheckLegacyAndVim9Failure(["call printf('%2$d%d', 1, 3)"], "E1400:")
+  call v9.CheckLegacyAndVim9Failure(["call printf('%d%2$d', 1, 3)"], "E1400:")
+  call v9.CheckLegacyAndVim9Failure(["call printf('%2$*1$d%d', 1, 3)"], "E1400:")
+  call v9.CheckLegacyAndVim9Failure(["call printf('%d%2$*1$d', 1, 3)"], "E1400:")
+  call v9.CheckLegacyAndVim9Failure(["call printf('%2$.*1$d%d', 1, 3)"], "E1400:")
+  call v9.CheckLegacyAndVim9Failure(["call printf('%d%2$.*1$d', 1, 3)"], "E1400:")
+  call v9.CheckLegacyAndVim9Failure(["call printf('%1$%')"], "E1400:")
+  call v9.CheckLegacyAndVim9Failure(["call printf('%1$')"], "E1400:")
+  call v9.CheckLegacyAndVim9Failure(["call printf('%1$_')"], "E1400:")
+  call v9.CheckLegacyAndVim9Failure(["call printf('%1$*3$.*d', 3)"], "E1400:")
+  call v9.CheckLegacyAndVim9Failure(["call printf('%1$*.*2$d', 3)"], "E1400:")
+  call v9.CheckLegacyAndVim9Failure(["call printf('%1$*.*d', 3)"], "E1400:")
+  call v9.CheckLegacyAndVim9Failure(["call printf('%*.*1$d', 3)"], "E1400:")
+  call v9.CheckLegacyAndVim9Failure(["call printf('%*1$.*d', 3)"], "E1400:")
+  call v9.CheckLegacyAndVim9Failure(["call printf('%*1$.*1$d', 3)"], "E1400:")
+
+  call v9.CheckLegacyAndVim9Failure(["call printf('%2$d', 3, 3)"], "E1401:")
+
+  call v9.CheckLegacyAndVim9Failure(["call printf('%2$*1$d %1$ld', 3, 3)"], "E1402:")
+  call v9.CheckLegacyAndVim9Failure(["call printf('%1$s %1$*1$d', 3)"], "E1402:")
+  call v9.CheckLegacyAndVim9Failure(["call printf('%1$p %1$*1$d', 3)"], "E1402:")
+  call v9.CheckLegacyAndVim9Failure(["call printf('%1$f %1$*1$d', 3)"], "E1402:")
+  call v9.CheckLegacyAndVim9Failure(["call printf('%1$lud %1$*1$d', 3)"], "E1402:")
+  call v9.CheckLegacyAndVim9Failure(["call printf('%1$llud %1$*1$d', 3)"], "E1402:")
+  call v9.CheckLegacyAndVim9Failure(["call printf('%1$lld %1$*1$d', 3)"], "E1402:")
+  call v9.CheckLegacyAndVim9Failure(["call printf('%1$s %1$*1$d', 3)"], "E1402:")
+  call v9.CheckLegacyAndVim9Failure(["call printf('%1$c %1$*1$d', 3)"], "E1402:")
+  call v9.CheckLegacyAndVim9Failure(["call printf('%1$ld %1$*1$d', 3)"], "E1402:")
+  call v9.CheckLegacyAndVim9Failure(["call printf('%1$ld %2$*1$d', 3, 3)"], "E1402:")
+  call v9.CheckLegacyAndVim9Failure(["call printf('%1$*1$ld', 3)"], "E1402:")
+  call v9.CheckLegacyAndVim9Failure(["call printf('%1$*1$.*1$ld', 3)"], "E1402:")
+
+  call v9.CheckLegacyAndVim9Failure(["call printf('%1$d%2$d', 3)"], "E1403:")
+
+  call v9.CheckLegacyAndVim9Failure(["call printf('%1$d %1$s', 3)"], "E1404:")
+  call v9.CheckLegacyAndVim9Failure(["call printf('%1$ld %1$s', 3)"], "E1404:")
+  call v9.CheckLegacyAndVim9Failure(["call printf('%1$ud %1$d', 3)"], "E1404:")
+  call v9.CheckLegacyAndVim9Failure(["call printf('%1$s %1$f', 3.0)"], "E1404:")
+  call v9.CheckLegacyAndVim9Failure(["call printf('%1$*1$d %1$ld', 3)"], "E1404:")
+  call v9.CheckLegacyAndVim9Failure(["call printf('%1$s %1$d', 3)"], "E1404:")
+  call v9.CheckLegacyAndVim9Failure(["call printf('%1$p %1$d', 3)"], "E1404:")
+  call v9.CheckLegacyAndVim9Failure(["call printf('%1$f %1$d', 3)"], "E1404:")
+  call v9.CheckLegacyAndVim9Failure(["call printf('%1$lud %1$d', 3)"], "E1404:")
+  call v9.CheckLegacyAndVim9Failure(["call printf('%1$llud %1$d', 3)"], "E1404:")
+  call v9.CheckLegacyAndVim9Failure(["call printf('%1$lld %1$d', 3)"], "E1404:")
+  call v9.CheckLegacyAndVim9Failure(["call printf('%1$s %1$d', 3)"], "E1404:")
+  call v9.CheckLegacyAndVim9Failure(["call printf('%1$c %1$d', 3)"], "E1404:")
+  call v9.CheckLegacyAndVim9Failure(["call printf('%1$ld %1$d', 3)"], "E1404:")
+
+  call v9.CheckLegacyAndVim9Failure(["call printf('%1$.2$d', 3)"], "E1405:")
+  call v9.CheckLegacyAndVim9Failure(["call printf('%01$d', 3)"], "E1405:")
+  call v9.CheckLegacyAndVim9Failure(["call printf('%01$0d', 3)"], "E1405:")
+  call v9.CheckLegacyAndVim9Failure(["call printf('%1$*2d', 3)"], "E1405:")
+  call v9.CheckLegacyAndVim9Failure(["call printf('%1$*3.*2$d', 3)"], "E1405:")
+  call v9.CheckLegacyAndVim9Failure(["call printf('%1$*3$.2$d', 3)"], "E1405:")
+  call v9.CheckLegacyAndVim9Failure(["call printf('%1$*3$.*2d', 3)"], "E1405:")
+  call v9.CheckLegacyAndVim9Failure(["call printf('%1$1$.5d', 5)"], "E1405:")
+  call v9.CheckLegacyAndVim9Failure(["call printf('%1$5.1$d', 5)"], "E1405:")
+  call v9.CheckLegacyAndVim9Failure(["call printf('%1$1$.1$d', 5)"], "E1405:")
+endfunc
+
+func Test_printf_pos_float()
+  let lines =<< trim END
+      call assert_equal('1.000000', printf('%1$f', 1))
+      call assert_equal('1.230000', printf('%1$f', 1.23))
+      call assert_equal('1.230000', printf('%1$F', 1.23))
+      call assert_equal('9999999.9', printf('%1$g', 9999999.9))
+      call assert_equal('9999999.9', printf('%1$G', 9999999.9))
+      call assert_equal('1.230000e+00', printf('%1$e', 1.23))
+      call assert_equal('1.230000E+00', printf('%1$E', 1.23))
+      call assert_equal('1.200000e-02', printf('%1$e', 0.012))
+      call assert_equal('-1.200000e-02', printf('%1$e', -0.012))
+      call assert_equal('0.33', printf('%1$.2f', 1.0 / 3.0))
+
+      #" When precision is 0, the dot should be omitted.
+      call assert_equal('  2', printf('%1$*2$.f', 7.0 / 3.0, 3))
+      call assert_equal('  2', printf('%2$*1$.f', 3, 7.0 / 3.0))
+      call assert_equal('  2', printf('%1$*2$.g', 7.0 / 3.0, 3))
+      call assert_equal('  2', printf('%2$*1$.g', 3, 7.0 / 3.0))
+      call assert_equal('  2e+00', printf('%1$*2$.e', 7.0 / 3.0, 7))
+      call assert_equal('  2e+00', printf('%2$*1$.e', 7, 7.0 / 3.0))
+
+      #" Float zero can be signed.
+      call assert_equal('+0.000000', printf('%1$+f', 0.0))
+      call assert_equal('0.000000', printf('%1$f', 1.0 / (1.0 / 0.0)))
+      call assert_equal('-0.000000', printf('%1$f', 1.0 / (-1.0 / 0.0)))
+      call assert_equal('0.0', printf('%1$s', 1.0 / (1.0 / 0.0)))
+      call assert_equal('-0.0', printf('%1$s', 1.0 / (-1.0 / 0.0)))
+      call assert_equal('0.0', printf('%1$S', 1.0 / (1.0 / 0.0)))
+      call assert_equal('-0.0', printf('%1$S', 1.0 / (-1.0 / 0.0)))
+
+      #" Float infinity can be signed.
+      call assert_equal('inf', printf('%1$f', 1.0 / 0.0))
+      call assert_equal('-inf', printf('%1$f', -1.0 / 0.0))
+      call assert_equal('inf', printf('%1$g', 1.0 / 0.0))
+      call assert_equal('-inf', printf('%1$g', -1.0 / 0.0))
+      call assert_equal('inf', printf('%1$e', 1.0 / 0.0))
+      call assert_equal('-inf', printf('%1$e', -1.0 / 0.0))
+      call assert_equal('INF', printf('%1$F', 1.0 / 0.0))
+      call assert_equal('-INF', printf('%1$F', -1.0 / 0.0))
+      call assert_equal('INF', printf('%1$E', 1.0 / 0.0))
+      call assert_equal('-INF', printf('%1$E', -1.0 / 0.0))
+      call assert_equal('INF', printf('%1$E', 1.0 / 0.0))
+      call assert_equal('-INF', printf('%1$G', -1.0 / 0.0))
+      call assert_equal('+inf', printf('%1$+f', 1.0 / 0.0))
+      call assert_equal('-inf', printf('%1$+f', -1.0 / 0.0))
+      call assert_equal(' inf', printf('%1$ f',  1.0 / 0.0))
+      call assert_equal('   inf', printf('%1$*2$f', 1.0 / 0.0, 6))
+      call assert_equal('  -inf', printf('%1$*2$f', -1.0 / 0.0, 6))
+      call assert_equal('   inf', printf('%1$*2$g', 1.0 / 0.0, 6))
+      call assert_equal('  -inf', printf('%1$*2$g', -1.0 / 0.0, 6))
+      call assert_equal('  +inf', printf('%1$+*2$f', 1.0 / 0.0, 6))
+      call assert_equal('   inf', printf('%1$ *2$f', 1.0 / 0.0, 6))
+      call assert_equal('  +inf', printf('%1$+0*2$f', 1.0 / 0.0, 6))
+      call assert_equal('inf   ', printf('%1$-*2$f', 1.0 / 0.0, 6))
+      call assert_equal('-inf  ', printf('%1$-*2$f', -1.0 / 0.0, 6))
+      call assert_equal('+inf  ', printf('%1$-+*2$f', 1.0 / 0.0, 6))
+      call assert_equal(' inf  ', printf('%1$- *2$f', 1.0 / 0.0, 6))
+      call assert_equal('-INF  ', printf('%1$-*2$F', -1.0 / 0.0, 6))
+      call assert_equal('+INF  ', printf('%1$-+*2$F', 1.0 / 0.0, 6))
+      call assert_equal(' INF  ', printf('%1$- *2$F', 1.0 / 0.0, 6))
+      call assert_equal('INF   ', printf('%1$-*2$G', 1.0 / 0.0, 6))
+      call assert_equal('-INF  ', printf('%1$-*2$G', -1.0 / 0.0, 6))
+      call assert_equal('INF   ', printf('%1$-*2$E', 1.0 / 0.0, 6))
+      call assert_equal('-INF  ', printf('%1$-*2$E', -1.0 / 0.0, 6))
+      call assert_equal('   inf', printf('%2$*1$f', 6, 1.0 / 0.0))
+      call assert_equal('  -inf', printf('%2$*1$f', 6, -1.0 / 0.0))
+      call assert_equal('   inf', printf('%2$*1$g', 6, 1.0 / 0.0))
+      call assert_equal('  -inf', printf('%2$*1$g', 6, -1.0 / 0.0))
+      call assert_equal('  +inf', printf('%2$+*1$f', 6, 1.0 / 0.0))
+      call assert_equal('   inf', printf('%2$ *1$f', 6, 1.0 / 0.0))
+      call assert_equal('  +inf', printf('%2$+0*1$f', 6, 1.0 / 0.0))
+      call assert_equal('inf   ', printf('%2$-*1$f', 6, 1.0 / 0.0))
+      call assert_equal('-inf  ', printf('%2$-*1$f', 6, -1.0 / 0.0))
+      call assert_equal('+inf  ', printf('%2$-+*1$f', 6, 1.0 / 0.0))
+      call assert_equal(' inf  ', printf('%2$- *1$f', 6, 1.0 / 0.0))
+      call assert_equal('-INF  ', printf('%2$-*1$F', 6, -1.0 / 0.0))
+      call assert_equal('+INF  ', printf('%2$-+*1$F', 6, 1.0 / 0.0))
+      call assert_equal(' INF  ', printf('%2$- *1$F', 6, 1.0 / 0.0))
+      call assert_equal('INF   ', printf('%2$-*1$G', 6, 1.0 / 0.0))
+      call assert_equal('-INF  ', printf('%2$-*1$G', 6, -1.0 / 0.0))
+      call assert_equal('INF   ', printf('%2$-*1$E', 6, 1.0 / 0.0))
+      call assert_equal('-INF  ', printf('%2$-*1$E', 6, -1.0 / 0.0))
+      call assert_equal('inf', printf('%1$s', 1.0 / 0.0))
+      call assert_equal('-inf', printf('%1$s', -1.0 / 0.0))
+
+      #" Test special case where max precision is truncated at 340.
+      call assert_equal('1.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', printf('%1$.*2$f', 1.0, 330))
+      call assert_equal('1.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', printf('%2$.*1$f', 330, 1.0))
+      call assert_equal('1.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', printf('%1$.*2$f', 1.0, 340))
+      call assert_equal('1.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', printf('%2$.*1$f', 340, 1.0))
+      call assert_equal('1.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', printf('%1$.*2$f', 1.0, 350))
+      call assert_equal('1.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', printf('%2$.*1$f', 350, 1.0))
+
+      #" Float nan (not a number) has no sign.
+      call assert_equal('nan', printf('%1$f', sqrt(-1.0)))
+      call assert_equal('nan', printf('%1$f', 0.0 / 0.0))
+      call assert_equal('nan', printf('%1$f', -0.0 / 0.0))
+      call assert_equal('nan', printf('%1$g', 0.0 / 0.0))
+      call assert_equal('nan', printf('%1$e', 0.0 / 0.0))
+      call assert_equal('NAN', printf('%1$F', 0.0 / 0.0))
+      call assert_equal('NAN', printf('%1$G', 0.0 / 0.0))
+      call assert_equal('NAN', printf('%1$E', 0.0 / 0.0))
+      call assert_equal('NAN', printf('%1$F', -0.0 / 0.0))
+      call assert_equal('NAN', printf('%1$G', -0.0 / 0.0))
+      call assert_equal('NAN', printf('%1$E', -0.0 / 0.0))
+      call assert_equal('   nan', printf('%1$*2$f', 0.0 / 0.0, 6))
+      call assert_equal('   nan', printf('%1$0*2$f', 0.0 / 0.0, 6))
+      call assert_equal('nan   ', printf('%1$-*2$f', 0.0 / 0.0, 6))
+      call assert_equal('nan   ', printf('%1$- *2$f', 0.0 / 0.0, 6))
+      call assert_equal('   nan', printf('%2$*1$f', 6, 0.0 / 0.0))
+      call assert_equal('   nan', printf('%2$0*1$f', 6, 0.0 / 0.0))
+      call assert_equal('nan   ', printf('%2$-*1$f', 6, 0.0 / 0.0))
+      call assert_equal('nan   ', printf('%2$- *1$f', 6, 0.0 / 0.0))
+      call assert_equal('nan', printf('%1$s', 0.0 / 0.0))
+      call assert_equal('nan', printf('%1$s', -0.0 / 0.0))
+      call assert_equal('nan', printf('%1$S', 0.0 / 0.0))
+      call assert_equal('nan', printf('%1$S', -0.0 / 0.0))
+  END
+  call v9.CheckLegacyAndVim9Success(lines)
+
+  call v9.CheckLegacyAndVim9Failure(['echo printf("%f", "a")'], 'E807:')
+endfunc
+
+func Test_printf_pos_errors()
+  call v9.CheckLegacyAndVim9Failure(['echo printf("%1$d", {})'], 'E728:')
+  call v9.CheckLegacyAndVim9Failure(['echo printf("%1$d", [])'], 'E745:')
+  call v9.CheckLegacyAndVim9Failure(['echo printf("%1$d", 1, 2)'], 'E767:')
+  call v9.CheckLegacyAndVim9Failure(['echo printf("%*d", 1)'], 'E766:')
+  call v9.CheckLegacyAndVim9Failure(['echo printf("%1$s")'], 'E1403:')
+  call v9.CheckLegacyAndVim9Failure(['echo printf("%1$d", 1.2)'], 'E805:')
+  call v9.CheckLegacyAndVim9Failure(['echo printf("%1$f")'], 'E1403:')
+endfunc
+
+func Test_printf_pos_64bit()
+  let lines =<< trim END
+      call assert_equal("123456789012345", printf('%1$d', 123456789012345))
+  END
+  call v9.CheckLegacyAndVim9Success(lines)
+endfunc
+
+func Test_printf_pos_spec_s()
+  let lines =<< trim END
+      #" number
+      call assert_equal("1234567890", printf('%1$s', 1234567890))
+
+      #" string
+      call assert_equal("abcdefgi", printf('%1$s', "abcdefgi"))
+
+      #" float
+      call assert_equal("1.23", printf('%1$s', 1.23))
+
+      #" list
+      VAR lvalue = [1, 'two', ['three', 4]]
+      call assert_equal(string(lvalue), printf('%1$s', lvalue))
+
+      #" dict
+      VAR dvalue = {'key1': 'value1', 'key2': ['list', 'lvalue'], 'key3': {'dict': 'lvalue'}}
+      call assert_equal(string(dvalue), printf('%1$s', dvalue))
+
+      #" funcref
+      call assert_equal('printf', printf('%1$s', 'printf'->function()))
+
+      #" partial
+      call assert_equal(string(function('printf', ['%1$s'])), printf('%1$s', function('printf', ['%1$s'])))
+  END
+  call v9.CheckLegacyAndVim9Success(lines)
+endfunc
+
+func Test_printf_pos_spec_b()
+  let lines =<< trim END
+      call assert_equal("0", printf('%1$b', 0))
+      call assert_equal("00001100", printf('%1$0*2$b', 12, 8))
+      call assert_equal("11111111", printf('%1$0*2$b', 0xff, 8))
+      call assert_equal("   1111011", printf('%1$*2$b', 123, 10))
+      call assert_equal("0001111011", printf('%1$0*2$b', 123, 10))
+      call assert_equal(" 0b1111011", printf('%1$#*2$b', 123, 10))
+      call assert_equal("0B01111011", printf('%1$#0*2$B', 123, 10))
+      call assert_equal("00001100", printf('%2$0*1$b', 8, 12))
+      call assert_equal("11111111", printf('%2$0*1$b', 8, 0xff))
+      call assert_equal("   1111011", printf('%2$*1$b', 10, 123))
+      call assert_equal("0001111011", printf('%2$0*1$b', 10, 123))
+      call assert_equal(" 0b1111011", printf('%2$#*1$b', 10, 123))
+      call assert_equal("0B01111011", printf('%2$#0*1$B', 10, 123))
+      call assert_equal("1001001100101100000001011010010", printf('%1$b', 1234567890))
+      call assert_equal("11100000100100010000110000011011101111101111001", printf('%1$b', 123456789012345))
+      call assert_equal("1111111111111111111111111111111111111111111111111111111111111111", printf('%1$b', -1))
+  END
+  call v9.CheckLegacyAndVim9Success(lines)
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
index d5091c4e9a3da36b20db1c111258c7702030e7cc..888e839511f8803149b392269679aea992a16123 100644 (file)
@@ -695,6 +695,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1704,
 /**/
     1703,
 /**/