vim-patch:9.0.1704: Cannot use positional arguments for printf() (#24719)

Problem: Cannot use positional arguments for printf()
Solution: Support positional arguments in string formatting

closes: vim/vim#12140

0c6181fec4

Co-authored-by: Christ van Willegen <cvwillegen@gmail.com>
This commit is contained in:
zeertzjq
2023-08-15 19:16:19 +08:00
committed by GitHub
parent 7aad4643f9
commit 842a47d6a4
9 changed files with 1529 additions and 77 deletions

View File

@@ -7251,7 +7251,11 @@ M.funcs = {
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:
@@ -7320,6 +7324,13 @@ M.funcs = {
<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* *printf-x* *printf-X*
@@ -7406,6 +7417,104 @@ M.funcs = {
The number of {exprN} arguments must exactly match the number
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: >vim
#, 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. >vim
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 >vim
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. >vim
echo printf("%1$*2$.*3$d", 1, 2, 3)
< 001 >vim
echo printf("%2$*3$.*1$d", 1, 2, 3)
< 2 >vim
echo printf("%3$*1$.*2$d", 1, 2, 3)
< 03 >vim
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: >vim
echo printf("%1$4.*2$f", 1.4142135, 6)
< 1.414214 >vim
echo printf("%1$*2$.4f", 1.4142135, 6)
< 1.4142 >vim
echo printf("%1$*2$.*3$f", 1.4142135, 6, 2)
< 1.41
*E1400*
You cannot mix positional and non-positional arguments: >vim
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: >vim
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: >vim
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: >vim
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: >vim
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: >vim
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: >vim
echo printf("%1$s %2$s %1$s", "One", "Two")
< One Two One
However, you can't use a different type the second time: >vim
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: >vim
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
]=],
name = 'printf',
params = { { 'fmt', 'any' }, { 'expr1', 'any' } },

View File

@@ -33,8 +33,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.

View File

@@ -32,6 +32,32 @@
#include "nvim/types.h"
#include "nvim/vim.h"
static char e_cannot_mix_positional_and_non_positional_str[]
= N_("E1400: Cannot mix positional and non-positional arguments: %s");
static char e_fmt_arg_nr_unused_str[]
= N_("E1401: format argument %d unused in $-style format: %s");
static char e_positional_num_field_spec_reused_str_str[]
= N_("E1402: Positional argument %d used as field width reused as different type: %s/%s");
static char e_positional_nr_out_of_bounds_str[]
= N_("E1403: Positional argument %d out of bounds: %s");
static char e_positional_arg_num_type_inconsistent_str_str[]
= N_("E1404: Positional argument %d type used inconsistently: %s/%s");
static char e_invalid_format_specifier_str[]
= N_("E1405: Invalid format specifier: %s");
static char typename_unknown[] = N_("unknown");
static char typename_int[] = N_("int");
static char typename_longint[] = N_("long int");
static char typename_longlongint[] = N_("long long int");
static char typename_unsignedint[] = N_("unsigned int");
static char typename_unsignedlongint[] = N_("unsigned long int");
static char typename_unsignedlonglongint[] = N_("unsigned long long int");
static char typename_pointer[] = N_("pointer");
static char typename_percent[] = N_("percent");
static char typename_char[] = N_("char");
static char typename_string[] = N_("string");
static char typename_float[] = N_("float");
/// Copy up to `len` bytes of `string` into newly allocated memory and
/// terminate with a NUL. The allocated memory always has size `len + 1`, even
/// when `string` is shorter.
@@ -717,6 +743,571 @@ int vim_vsnprintf(char *str, size_t str_m, const char *fmt, va_list ap)
return vim_vsnprintf_typval(str, str_m, fmt, ap, NULL);
}
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
static int format_typeof(const char *type, bool usetvs)
FUNC_ATTR_NONNULL_ALL
{
// 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 = long long
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 (usetvs) {
switch (fmt_spec) {
case 'd':
case 'u':
case 'o':
case 'x':
case 'X':
if (length_modifier == '\0') {
length_modifier = 'L';
}
}
}
// 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;
}
static char *format_typename(const char *type)
FUNC_ATTR_NONNULL_ALL
{
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);
}
static int adjust_types(const char ***ap_types, int arg, int *num_posarg, const char *type)
FUNC_ATTR_NONNULL_ALL
{
if (*ap_types == NULL || *num_posarg < arg) {
const char **new_types = *ap_types == NULL
? xcalloc(sizeof(const char *), (size_t)arg)
: xrealloc(*ap_types, (size_t)arg * sizeof(const char *));
for (int 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;
}
static int parse_fmt_types(const char ***ap_types, int *num_posarg, const char *fmt, typval_T *tvs)
FUNC_ATTR_NONNULL_ARG(1, 2)
{
const char *p = fmt;
const char *arg = NULL;
int any_pos = 0;
int any_arg = 0;
#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;
p++; // skip '%'
// First check to see if we find a positional
// argument specifier
const char *ptype = p;
while (ascii_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 uj = (unsigned)(*p++ - '0');
while (ascii_isdigit((int)(*p))) {
uj = 10 * uj + (unsigned)(*p++ - '0');
}
pos_arg = (int)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 (ascii_isdigit((int)(*p))) {
// Positional argument field width
unsigned uj = (unsigned)(*p++ - '0');
while (ascii_isdigit((int)(*p))) {
uj = 10 * uj + (unsigned)(*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, (int)uj, num_posarg, arg) == FAIL) {
goto error;
}
}
} else {
any_arg = 1;
CHECK_POS_ARG;
}
} else if (ascii_isdigit((int)(*(arg = p)))) {
// size_t could be wider than unsigned int; make sure we treat
// argument like common implementations do
unsigned uj = (unsigned)(*p++ - '0');
while (ascii_isdigit((int)(*p))) {
uj = 10 * uj + (unsigned)(*p++ - '0');
}
if (*p == '$') {
semsg(_(e_invalid_format_specifier_str), fmt);
goto error;
}
}
// parse precision
if (*p == '.') {
p++;
if (*(arg = p) == '*') {
p++;
if (ascii_isdigit((int)(*p))) {
// Parse precision
unsigned uj = (unsigned)(*p++ - '0');
while (ascii_isdigit((int)(*p))) {
uj = 10 * uj + (unsigned)(*p++ - '0');
}
if (*p == '$') {
any_pos = 1;
CHECK_POS_ARG;
p++;
if (adjust_types(ap_types, (int)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 (ascii_isdigit((int)(*(arg = p)))) {
// size_t could be wider than unsigned int; make sure we
// treat argument like common implementations do
unsigned uj = (unsigned)(*p++ - '0');
while (ascii_isdigit((int)(*p))) {
uj = 10 * uj + (unsigned)(*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 = long long
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 (int 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 (tvs != NULL && tvs[arg_idx].v_type == VAR_UNKNOWN) {
semsg(_(e_positional_nr_out_of_bounds_str), arg_idx + 1, fmt);
goto error;
}
}
return OK;
error:
xfree(*ap_types);
*ap_types = NULL;
*num_posarg = 0;
return FAIL;
}
static void skip_to_arg(const char **ap_types, va_list ap_start, va_list *ap, int *arg_idx,
int *arg_cur)
FUNC_ATTR_NONNULL_ARG(3, 4, 5)
{
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)++) {
assert(ap_types != NULL);
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, const 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);
break;
case TYPE_LONGLONGINT:
va_arg(*ap, long long); // NOLINT(runtime/int)
break;
case TYPE_UNSIGNEDINT:
va_arg(*ap, unsigned);
break;
case TYPE_UNSIGNEDLONGINT:
va_arg(*ap, unsigned long);
break;
case TYPE_UNSIGNEDLONGLONGINT:
va_arg(*ap, unsigned long long); // NOLINT(runtime/int)
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)++;
}
/// Write formatted value to the string
///
/// @param[out] str String to write to.
@@ -728,12 +1319,23 @@ int vim_vsnprintf(char *str, size_t str_m, const char *fmt, va_list ap)
///
/// @return Number of bytes excluding NUL byte that would be written to the
/// string if str_m was greater or equal to the return value.
int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, typval_T *const tvs)
int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap_start,
typval_T *const tvs)
{
size_t str_l = 0;
bool str_avail = str_l < str_m;
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) {
p = "";
@@ -789,8 +1391,31 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t
// buffer for 's' and 'S' specs
char *tofree = NULL;
// variable for positional arg
int pos_arg = -1;
p++; // skip '%'
// First check to see if we find a positional
// argument specifier
const char *ptype = p;
while (ascii_isdigit(*ptype)) {
ptype++;
}
if (*ptype == '$') {
// Positional argument
unsigned uj = (unsigned)(*p++ - '0');
while (ascii_isdigit((int)(*p))) {
uj = 10 * uj + (unsigned)(*p++ - '0');
}
pos_arg = (int)uj;
p++;
}
// parse flags
while (true) {
switch (*p) {
@@ -817,7 +1442,24 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t
// parse field width
if (*p == '*') {
p++;
const int j = tvs ? (int)tv_nr(tvs, &arg_idx) : va_arg(ap, int);
if (ascii_isdigit((int)(*p))) {
// Positional argument field width
unsigned uj = (unsigned)(*p++ - '0');
while (ascii_isdigit((int)(*p))) {
uj = 10 * uj + (unsigned)(*p++ - '0');
}
arg_idx = (int)uj;
p++;
}
const int j = (tvs
? (int)tv_nr(tvs, &arg_idx)
: (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur),
va_arg(ap, int)));
if (j >= 0) {
min_field_width = (size_t)j;
} else {
@@ -839,16 +1481,8 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t
if (*p == '.') {
p++;
precision_specified = 1;
if (*p == '*') {
const int j = tvs ? (int)tv_nr(tvs, &arg_idx) : va_arg(ap, int);
p++;
if (j >= 0) {
precision = (size_t)j;
} else {
precision_specified = 0;
precision = 0;
}
} else if (ascii_isdigit((int)(*p))) {
if (ascii_isdigit((int)(*p))) {
// size_t could be wider than unsigned int; make sure we
// treat argument like common implementations do
unsigned uj = (unsigned)(*p++ - '0');
@@ -857,6 +1491,32 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t
uj = 10 * uj + (unsigned)(*p++ - '0');
}
precision = uj;
} else if (*p == '*') {
p++;
if (ascii_isdigit((int)(*p))) {
// positional argument
unsigned uj = (unsigned)(*p++ - '0');
while (ascii_isdigit((int)(*p))) {
uj = 10 * uj + (unsigned)(*p++ - '0');
}
arg_idx = (int)uj;
p++;
}
const int j = (tvs
? (int)tv_nr(tvs, &arg_idx)
: (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur),
va_arg(ap, int)));
if (j >= 0) {
precision = (size_t)j;
} else {
precision_specified = 0;
precision = 0;
}
}
}
@@ -864,8 +1524,9 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t
if (*p == 'h' || *p == 'l' || *p == 'z') {
length_modifier = *p;
p++;
if (length_modifier == 'l' && *p == 'l') { // ll, encoded as 2
length_modifier = '2';
if (length_modifier == 'l' && *p == 'l') {
// double l = long long
length_modifier = 'L';
p++;
}
}
@@ -895,10 +1556,14 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t
case 'x':
case 'X':
if (tvs && length_modifier == '\0') {
length_modifier = '2';
length_modifier = 'L';
}
}
if (pos_arg != -1) {
arg_idx = pos_arg;
}
// get parameter value, do initial processing
switch (fmt_spec) {
// '%' and 'c' behave similar to 's' regarding flags and field widths
@@ -913,7 +1578,11 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t
break;
case 'c': {
const int j = tvs ? (int)tv_nr(tvs, &arg_idx) : va_arg(ap, int);
const int j = (tvs
? (int)tv_nr(tvs, &arg_idx)
: (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur),
va_arg(ap, int)));
// standard demands unsigned char
uchar_arg = (unsigned char)j;
str_arg = (char *)&uchar_arg;
@@ -922,8 +1591,11 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t
case 's':
case 'S':
str_arg = tvs ? tv_str(tvs, &arg_idx, &tofree)
: va_arg(ap, const char *);
str_arg = (tvs
? tv_str(tvs, &arg_idx, &tofree)
: (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur),
va_arg(ap, const char *)));
if (!str_arg) {
str_arg = "[NULL]";
str_arg_l = 6;
@@ -990,7 +1662,11 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t
const void *ptr_arg = NULL;
if (fmt_spec == 'p') {
ptr_arg = tvs ? tv_ptr(tvs, &arg_idx) : va_arg(ap, void *);
ptr_arg = (tvs
? tv_ptr(tvs, &arg_idx)
: (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur),
va_arg(ap, void *)));
if (ptr_arg) {
arg_sign = 1;
}
@@ -998,23 +1674,36 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t
// signed
switch (length_modifier) {
case '\0':
arg = (int)(tvs ? tv_nr(tvs, &arg_idx) : va_arg(ap, int));
arg = (tvs
? (int)tv_nr(tvs, &arg_idx)
: (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur),
va_arg(ap, int)));
break;
case 'h':
// char and short arguments are passed as int16_t
arg = (int16_t)(tvs ? tv_nr(tvs, &arg_idx) : va_arg(ap, int));
arg = (int16_t)
(tvs
? (int)tv_nr(tvs, &arg_idx)
: (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur),
va_arg(ap, int)));
break;
case 'l':
arg = (tvs ? (long)tv_nr(tvs, &arg_idx) : va_arg(ap, long));
arg = (tvs
? (long)tv_nr(tvs, &arg_idx)
: (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur),
va_arg(ap, long)));
break;
case '2':
arg = (
tvs
? (long long)tv_nr(tvs, &arg_idx) // NOLINT (runtime/int)
: va_arg(ap, long long)); // NOLINT (runtime/int)
case 'L':
arg = (tvs
? (long long)tv_nr(tvs, &arg_idx) // NOLINT(runtime/int)
: (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur),
va_arg(ap, long long))); // NOLINT(runtime/int)
break;
case 'z':
arg = (tvs ? (ptrdiff_t)tv_nr(tvs, &arg_idx) : va_arg(ap, ptrdiff_t));
arg = (tvs
? (ptrdiff_t)tv_nr(tvs, &arg_idx)
: (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur),
va_arg(ap, ptrdiff_t)));
break;
}
if (arg > 0) {
@@ -1026,23 +1715,35 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t
// unsigned
switch (length_modifier) {
case '\0':
uarg = (unsigned)(tvs ? tv_nr(tvs, &arg_idx) : va_arg(ap, unsigned));
uarg = (tvs
? (unsigned)tv_nr(tvs, &arg_idx)
: (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur),
va_arg(ap, unsigned)));
break;
case 'h':
uarg = (uint16_t)(tvs ? tv_nr(tvs, &arg_idx) : va_arg(ap, unsigned));
uarg = (uint16_t)
(tvs
? (unsigned)tv_nr(tvs, &arg_idx)
: (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur),
va_arg(ap, unsigned)));
break;
case 'l':
uarg = (tvs ? (unsigned long)tv_nr(tvs, &arg_idx) : va_arg(ap, unsigned long));
uarg = (tvs
? (unsigned long)tv_nr(tvs, &arg_idx)
: (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur),
va_arg(ap, unsigned long)));
break;
case '2':
uarg = (uintmax_t)(unsigned long long)( // NOLINT (runtime/int)
tvs
? ((unsigned long long) // NOLINT (runtime/int)
tv_nr(tvs, &arg_idx))
: va_arg(ap, unsigned long long)); // NOLINT (runtime/int)
case 'L':
uarg = (tvs
? (unsigned long long)tv_nr(tvs, &arg_idx) // NOLINT(runtime/int)
: (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur),
va_arg(ap, unsigned long long))); // NOLINT(runtime/int)
break;
case 'z':
uarg = (tvs ? (size_t)tv_nr(tvs, &arg_idx) : va_arg(ap, size_t));
uarg = (tvs
? (size_t)tv_nr(tvs, &arg_idx)
: (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur),
va_arg(ap, size_t)));
break;
}
arg_sign = (uarg != 0);
@@ -1177,7 +1878,11 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t
char format[40];
int remove_trailing_zeroes = false;
double f = tvs ? tv_float(tvs, &arg_idx) : va_arg(ap, double);
double f = (tvs
? tv_float(tvs, &arg_idx)
: (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur),
va_arg(ap, double)));
double abs_f = f < 0 ? -f : f;
if (fmt_spec == 'g' || fmt_spec == 'G') {
@@ -1395,10 +2100,14 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t
str[str_l <= str_m - 1 ? str_l : str_m - 1] = '\0';
}
if (tvs && 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(_("E767: Too many arguments to printf()"));
}
xfree(ap_types);
va_end(ap);
// return the number of characters formatted (excluding trailing nul
// character); that is, the number of characters that would have been
// written to the buffer if it were large enough.