diff options
-rw-r--r-- | runtime/doc/builtin.txt | 110 | ||||
-rw-r--r-- | src/errors.h | 14 | ||||
-rw-r--r-- | src/globals.h | 13 | ||||
-rw-r--r-- | src/message_test.c | 201 | ||||
-rw-r--r-- | src/po/check.vim | 9 | ||||
-rw-r--r-- | src/strings.c | 784 | ||||
-rw-r--r-- | src/testdir/Make_all.mak | 1 | ||||
-rw-r--r-- | src/testdir/test_expr.vim | 2 | ||||
-rw-r--r-- | src/testdir/test_format.vim | 361 | ||||
-rw-r--r-- | src/version.c | 2 |
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; + // 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'); |