0
0
mirror of https://github.com/vim/vim.git synced 2025-09-02 21:13:50 -04:00

patch 9.0.1704: Cannot use positional arguments for printf()

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>
This commit is contained in:
Christ van Willegen 2023-08-13 18:03:14 +02:00 committed by Christian Brabandt
parent 1688938dd5
commit 0c6181fec4
No known key found for this signature in database
GPG Key ID: F3F92DA383FDDE09
10 changed files with 1473 additions and 34 deletions

View File

@ -6590,7 +6590,11 @@ printf({fmt}, {expr1} ...) *printf()*
The "%" starts a conversion specification. The following The "%" starts a conversion specification. The following
arguments appear in sequence: 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 flags
Zero or more of the following 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 < This limits the length of the text used from "line" to
"width" bytes. "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: The conversion specifiers and their meanings are:
*printf-d* *printf-b* *printf-B* *printf-o* *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 of "%" items. If there are not sufficient or too many
arguments an error is given. Up to 18 arguments can be used. 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()* prompt_getprompt({buf}) *prompt_getprompt()*
Returns the effective prompt text for buffer {buf}. {buf} can Returns the effective prompt text for buffer {buf}. {buf} can

View File

@ -3477,3 +3477,17 @@ EXTERN char e_incomplete_type[]
#endif #endif
EXTERN char e_warning_pointer_block_corrupted[] EXTERN char e_warning_pointer_block_corrupted[]
INIT(= N_("E1364: 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

View 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_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 * When ":global" is used to number of substitutions and changed lines is
* accumulated until it's finished. * accumulated until it's finished.

View File

@ -39,6 +39,9 @@
char *fmt_012p = "%012p"; char *fmt_012p = "%012p";
char *fmt_5S = "%5S"; char *fmt_5S = "%5S";
char *fmt_06b = "%06b"; 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(). * Test trunc_string().
@ -181,6 +184,11 @@ test_vim_snprintf(void)
// buffer and its content should then never be used. // buffer and its content should then never be used.
char *buf = malloc(bsize); 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); n = vim_snprintf(buf, bsize, "%d", 1234567);
assert(n == 7); assert(n == 7);
assert(bsize == 0 || STRNCMP(buf, "1234567", bsize_int) == 0); 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 || STRNCMP(buf, "001100", bsize_int) == 0);
assert(bsize == 0 || buf[MIN(n, 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); n = vim_snprintf(buf, bsize, "%f", 1.234);
assert(n == 8); assert(n == 8);
assert(bsize == 0 || STRNCMP(buf, "1.234000", bsize_int) == 0); assert(bsize == 0 || STRNCMP(buf, "1.234000", bsize_int) == 0);
@ -240,6 +254,7 @@ test_vim_snprintf(void)
assert(n == 9); assert(n == 9);
assert(bsize == 0 || STRNCMP(buf, "-0.000000", bsize_int) == 0); assert(bsize == 0 || STRNCMP(buf, "-0.000000", bsize_int) == 0);
assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0');
#endif
n = vim_snprintf(buf, bsize, "%s", "漢語"); n = vim_snprintf(buf, bsize, "%s", "漢語");
assert(n == 6); 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 int
main(int argc, char **argv) main(int argc, char **argv)
{ {
@ -317,11 +516,13 @@ main(int argc, char **argv)
test_trunc_string(); test_trunc_string();
test_trunc_string_mbyte(); test_trunc_string_mbyte();
test_vim_snprintf(); test_vim_snprintf();
test_vim_snprintf_positional();
set_option_value_give_err((char_u *)"encoding", 0, (char_u *)"latin1", 0); set_option_value_give_err((char_u *)"encoding", 0, (char_u *)"latin1", 0);
init_chartab(); init_chartab();
test_trunc_string(); test_trunc_string();
test_vim_snprintf(); test_vim_snprintf();
test_vim_snprintf_positional();
return 0; return 0;
} }

View File

@ -30,8 +30,15 @@ func! GetMline()
" remove '%' used for plural forms. " remove '%' used for plural forms.
let idline = substitute(idline, '\\nPlural-Forms: .\+;\\n', '', '') 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. " 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 endfunc
" This only works when 'wrapscan' is not set. " This only works when 'wrapscan' is not set.

View File

@ -2242,17 +2242,665 @@ vim_vsnprintf(
return vim_vsnprintf_typval(str, str_m, fmt, ap, NULL); 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 int
vim_vsnprintf_typval( vim_vsnprintf_typval(
char *str, char *str,
size_t str_m, size_t str_m,
const char *fmt, const char *fmt,
va_list ap, va_list ap_start,
typval_T *tvs) typval_T *tvs)
{ {
size_t str_l = 0; size_t str_l = 0;
const char *p = fmt; const char *p = fmt;
int arg_cur = 0;
int num_posarg = 0;
int arg_idx = 1; 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) if (p == NULL)
p = ""; p = "";
@ -2316,9 +2964,32 @@ vim_vsnprintf_typval(
// buffer for 's' and 'S' specs // buffer for 's' and 'S' specs
char_u *tofree = NULL; char_u *tofree = NULL;
// variables for positional arg
int pos_arg = -1;
const char *ptype;
p++; // skip '%' 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 // parse flags
while (*p == '0' || *p == '-' || *p == '+' || *p == ' ' while (*p == '0' || *p == '-' || *p == '+' || *p == ' '
|| *p == '#' || *p == '\'') || *p == '#' || *p == '\'')
@ -2346,11 +3017,26 @@ vim_vsnprintf_typval(
int j; int j;
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');
arg_idx = uj;
++p;
}
j = j =
# if defined(FEAT_EVAL) # if defined(FEAT_EVAL)
tvs != NULL ? tv_nr(tvs, &arg_idx) : tvs != NULL ? tv_nr(tvs, &arg_idx) :
# endif # endif
va_arg(ap, int); (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur),
va_arg(ap, int));
if (j >= 0) if (j >= 0)
min_field_width = j; min_field_width = j;
else else
@ -2375,25 +3061,8 @@ vim_vsnprintf_typval(
{ {
p++; p++;
precision_specified = 1; precision_specified = 1;
if (*p == '*')
{
int j;
j = if (VIM_ISDIGIT((int)(*p)))
# if defined(FEAT_EVAL)
tvs != NULL ? tv_nr(tvs, &arg_idx) :
# endif
va_arg(ap, int);
p++;
if (j >= 0)
precision = j;
else
{
precision_specified = 0;
precision = 0;
}
}
else if (VIM_ISDIGIT((int)(*p)))
{ {
// size_t could be wider than unsigned int; make sure we // size_t could be wider than unsigned int; make sure we
// treat argument like common implementations do // treat argument like common implementations do
@ -2403,6 +3072,39 @@ vim_vsnprintf_typval(
uj = 10 * uj + (unsigned int)(*p++ - '0'); uj = 10 * uj + (unsigned int)(*p++ - '0');
precision = uj; 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
(skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur),
va_arg(ap, int));
if (j >= 0)
precision = j;
else
{
precision_specified = 0;
precision = 0;
}
}
} }
// parse 'h', 'l' and 'll' length modifiers // parse 'h', 'l' and 'll' length modifiers
@ -2438,6 +3140,9 @@ vim_vsnprintf_typval(
} }
# endif # endif
if (pos_arg != -1)
arg_idx = pos_arg;
// get parameter value, do initial processing // get parameter value, do initial processing
switch (fmt_spec) switch (fmt_spec)
{ {
@ -2462,7 +3167,9 @@ vim_vsnprintf_typval(
# if defined(FEAT_EVAL) # if defined(FEAT_EVAL)
tvs != NULL ? tv_nr(tvs, &arg_idx) : tvs != NULL ? tv_nr(tvs, &arg_idx) :
# endif # 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 // standard demands unsigned char
uchar_arg = (unsigned char)j; uchar_arg = (unsigned char)j;
str_arg = (char *)&uchar_arg; str_arg = (char *)&uchar_arg;
@ -2475,7 +3182,9 @@ vim_vsnprintf_typval(
# if defined(FEAT_EVAL) # if defined(FEAT_EVAL)
tvs != NULL ? tv_str(tvs, &arg_idx, &tofree) : tvs != NULL ? tv_str(tvs, &arg_idx, &tofree) :
# endif # 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) if (str_arg == NULL)
{ {
str_arg = "[NULL]"; str_arg = "[NULL]";
@ -2570,7 +3279,9 @@ vim_vsnprintf_typval(
tvs != NULL ? (void *)tv_str(tvs, &arg_idx, tvs != NULL ? (void *)tv_str(tvs, &arg_idx,
NULL) : NULL) :
# endif # 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) if (ptr_arg != NULL)
arg_sign = 1; arg_sign = 1;
} }
@ -2581,7 +3292,9 @@ vim_vsnprintf_typval(
tvs != NULL ? tvs != NULL ?
(uvarnumber_T)tv_nr(tvs, &arg_idx) : (uvarnumber_T)tv_nr(tvs, &arg_idx) :
# endif # 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) if (bin_arg != 0)
arg_sign = 1; arg_sign = 1;
} }
@ -2597,7 +3310,9 @@ vim_vsnprintf_typval(
# if defined(FEAT_EVAL) # if defined(FEAT_EVAL)
tvs != NULL ? tv_nr(tvs, &arg_idx) : tvs != NULL ? tv_nr(tvs, &arg_idx) :
# endif # 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) if (int_arg > 0)
arg_sign = 1; arg_sign = 1;
else if (int_arg < 0) else if (int_arg < 0)
@ -2608,7 +3323,9 @@ vim_vsnprintf_typval(
# if defined(FEAT_EVAL) # if defined(FEAT_EVAL)
tvs != NULL ? tv_nr(tvs, &arg_idx) : tvs != NULL ? tv_nr(tvs, &arg_idx) :
# endif # 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) if (long_arg > 0)
arg_sign = 1; arg_sign = 1;
else if (long_arg < 0) else if (long_arg < 0)
@ -2619,7 +3336,9 @@ vim_vsnprintf_typval(
# if defined(FEAT_EVAL) # if defined(FEAT_EVAL)
tvs != NULL ? tv_nr(tvs, &arg_idx) : tvs != NULL ? tv_nr(tvs, &arg_idx) :
# endif # 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) if (llong_arg > 0)
arg_sign = 1; arg_sign = 1;
else if (llong_arg < 0) else if (llong_arg < 0)
@ -2639,7 +3358,9 @@ vim_vsnprintf_typval(
tvs != NULL ? (unsigned) tvs != NULL ? (unsigned)
tv_nr(tvs, &arg_idx) : tv_nr(tvs, &arg_idx) :
# endif # 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) if (uint_arg != 0)
arg_sign = 1; arg_sign = 1;
break; break;
@ -2649,7 +3370,9 @@ vim_vsnprintf_typval(
tvs != NULL ? (unsigned long) tvs != NULL ? (unsigned long)
tv_nr(tvs, &arg_idx) : tv_nr(tvs, &arg_idx) :
# endif # 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) if (ulong_arg != 0)
arg_sign = 1; arg_sign = 1;
break; break;
@ -2659,7 +3382,9 @@ vim_vsnprintf_typval(
tvs != NULL ? (uvarnumber_T) tvs != NULL ? (uvarnumber_T)
tv_nr(tvs, &arg_idx) : tv_nr(tvs, &arg_idx) :
# endif # 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) if (ullong_arg != 0)
arg_sign = 1; arg_sign = 1;
break; break;
@ -2859,7 +3584,9 @@ vim_vsnprintf_typval(
# if defined(FEAT_EVAL) # if defined(FEAT_EVAL)
tvs != NULL ? tv_float(tvs, &arg_idx) : tvs != NULL ? tv_float(tvs, &arg_idx) :
# endif # 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; abs_f = f < 0 ? -f : f;
if (fmt_spec == 'g' || fmt_spec == 'G') 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'; 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)); emsg(_(e_too_many_arguments_to_printf));
vim_free(ap_types);
va_end(ap);
// Return the number of characters formatted (excluding trailing nul // Return the number of characters formatted (excluding trailing nul
// character), that is, the number of characters that would have been // character), that is, the number of characters that would have been
// written to the buffer if it were large enough. // written to the buffer if it were large enough.

View File

@ -150,6 +150,7 @@ NEW_TESTS = \
test_fnameescape \ test_fnameescape \
test_fnamemodify \ test_fnamemodify \
test_fold \ test_fold \
test_format \
test_functions \ test_functions \
test_function_lists \ test_function_lists \
test_ga \ test_ga \

View File

@ -291,6 +291,8 @@ func Test_printf_misc()
let lines =<< trim END let lines =<< trim END
call assert_equal('123', printf('123')) 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('%d', 123))
call assert_equal('123', printf('%i', 123)) call assert_equal('123', printf('%i', 123))
call assert_equal('123', printf('%D', 123)) call assert_equal('123', printf('%D', 123))

361
src/testdir/test_format.vim Normal file
View File

@ -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

View File

@ -695,6 +695,8 @@ static char *(features[]) =
static int included_patches[] = static int included_patches[] =
{ /* Add new patch number below this line */ { /* Add new patch number below this line */
/**/
1704,
/**/ /**/
1703, 1703,
/**/ /**/