summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChrist van Willegen <cvwillegen@gmail.com>2023-08-13 18:03:14 +0200
committerChristian Brabandt <cb@256bit.org>2023-08-13 18:06:00 +0200
commit0c6181fec4c362eb9682d5af583341eb20cb1af5 (patch)
treee28ab76bd05d019ae45dee9286ab32f827513cc4
parent1688938dd5ac78ab67e54299b9d5b93499dba762 (diff)
patch 9.0.1704: Cannot use positional arguments for printf()v9.0.1704
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>
-rw-r--r--runtime/doc/builtin.txt110
-rw-r--r--src/errors.h14
-rw-r--r--src/globals.h13
-rw-r--r--src/message_test.c201
-rw-r--r--src/po/check.vim9
-rw-r--r--src/strings.c784
-rw-r--r--src/testdir/Make_all.mak1
-rw-r--r--src/testdir/test_expr.vim2
-rw-r--r--src/testdir/test_format.vim361
-rw-r--r--src/version.c2
10 files changed, 1468 insertions, 29 deletions
diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt
index 979f46f2dc..96db898b03 100644
--- a/runtime/doc/builtin.txt
+++ b/runtime/doc/builtin.txt
@@ -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
diff --git a/src/errors.h b/src/errors.h
index 9b40cb2549..6ef47000a9 100644
--- a/src/errors.h
+++ b/src/errors.h
@@ -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
diff --git a/src/globals.h b/src/globals.h
index 69b4343d61..6a68c3acbb 100644
--- a/src/globals.h
+++ b/src/globals.h
@@ -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.
diff --git a/src/message_test.c b/src/message_test.c
index 21ec4db8d3..4ad8041a46 100644
--- a/src/message_test.c
+++ b/src/message_test.c
@@ -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;
}
diff --git a/src/po/check.vim b/src/po/check.vim
index b2a5fd582e..917c648494 100644
--- a/src/po/check.vim
+++ b/src/po/check.vim
@@ -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.
diff --git a/src/strings.c b/src/strings.c
index e3be3354d3..ead59164e4 100644
--- a/src/strings.c
+++ b/src/strings.c
@@ -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