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

111
runtime/doc/builtin.txt generated
View File

@@ -5010,7 +5010,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:
@@ -5079,6 +5083,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* *printf-x* *printf-X*
@@ -5166,6 +5177,104 @@ 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: >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
prompt_getprompt({buf}) *prompt_getprompt()*
Returns the effective prompt text for buffer {buf}. {buf} can
be a buffer name or number. See |prompt-buffer|.

View File

@@ -5989,7 +5989,11 @@ function vim.fn.prevnonblank(lnum) end
--- 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:
@@ -6058,6 +6062,13 @@ function vim.fn.prevnonblank(lnum) end
--- <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*
@@ -6145,6 +6156,104 @@ function vim.fn.prevnonblank(lnum) end
--- 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
---
--- @param fmt any
--- @param expr1? any
--- @return any

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.

View File

@@ -239,6 +239,8 @@ func Test_printf_misc()
let lines =<< trim END
call assert_equal('123', printf('123'))
call assert_equal('', printf('%'))
call assert_equal('', printf('%.0d', 0))
call assert_equal('123', printf('%d', 123))
call assert_equal('123', printf('%i', 123))
call assert_equal('123', printf('%D', 123))

View File

@@ -0,0 +1,361 @@
" Tests for expressions.
source check.vim
source vim9.vim
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 CheckLegacyAndVim9Success(lines)
call CheckLegacyAndVim9Failure(["call printf('%1$d%2$d', 1, 3, 4)"], "E767:")
call CheckLegacyAndVim9Failure(["call printf('%2$d%d', 1, 3)"], "E1400:")
call CheckLegacyAndVim9Failure(["call printf('%d%2$d', 1, 3)"], "E1400:")
call CheckLegacyAndVim9Failure(["call printf('%2$*1$d%d', 1, 3)"], "E1400:")
call CheckLegacyAndVim9Failure(["call printf('%d%2$*1$d', 1, 3)"], "E1400:")
call CheckLegacyAndVim9Failure(["call printf('%2$.*1$d%d', 1, 3)"], "E1400:")
call CheckLegacyAndVim9Failure(["call printf('%d%2$.*1$d', 1, 3)"], "E1400:")
call CheckLegacyAndVim9Failure(["call printf('%1$%')"], "E1400:")
call CheckLegacyAndVim9Failure(["call printf('%1$')"], "E1400:")
call CheckLegacyAndVim9Failure(["call printf('%1$_')"], "E1400:")
call CheckLegacyAndVim9Failure(["call printf('%1$*3$.*d', 3)"], "E1400:")
call CheckLegacyAndVim9Failure(["call printf('%1$*.*2$d', 3)"], "E1400:")
call CheckLegacyAndVim9Failure(["call printf('%1$*.*d', 3)"], "E1400:")
call CheckLegacyAndVim9Failure(["call printf('%*.*1$d', 3)"], "E1400:")
call CheckLegacyAndVim9Failure(["call printf('%*1$.*d', 3)"], "E1400:")
call CheckLegacyAndVim9Failure(["call printf('%*1$.*1$d', 3)"], "E1400:")
call CheckLegacyAndVim9Failure(["call printf('%2$d', 3, 3)"], "E1401:")
call CheckLegacyAndVim9Failure(["call printf('%2$*1$d %1$ld', 3, 3)"], "E1402:")
call CheckLegacyAndVim9Failure(["call printf('%1$s %1$*1$d', 3)"], "E1402:")
call CheckLegacyAndVim9Failure(["call printf('%1$p %1$*1$d', 3)"], "E1402:")
call CheckLegacyAndVim9Failure(["call printf('%1$f %1$*1$d', 3)"], "E1402:")
call CheckLegacyAndVim9Failure(["call printf('%1$lud %1$*1$d', 3)"], "E1402:")
call CheckLegacyAndVim9Failure(["call printf('%1$llud %1$*1$d', 3)"], "E1402:")
call CheckLegacyAndVim9Failure(["call printf('%1$lld %1$*1$d', 3)"], "E1402:")
call CheckLegacyAndVim9Failure(["call printf('%1$s %1$*1$d', 3)"], "E1402:")
call CheckLegacyAndVim9Failure(["call printf('%1$c %1$*1$d', 3)"], "E1402:")
call CheckLegacyAndVim9Failure(["call printf('%1$ld %1$*1$d', 3)"], "E1402:")
call CheckLegacyAndVim9Failure(["call printf('%1$ld %2$*1$d', 3, 3)"], "E1402:")
call CheckLegacyAndVim9Failure(["call printf('%1$*1$ld', 3)"], "E1402:")
call CheckLegacyAndVim9Failure(["call printf('%1$*1$.*1$ld', 3)"], "E1402:")
call CheckLegacyAndVim9Failure(["call printf('%1$d%2$d', 3)"], "E1403:")
call CheckLegacyAndVim9Failure(["call printf('%1$d %1$s', 3)"], "E1404:")
call CheckLegacyAndVim9Failure(["call printf('%1$ld %1$s', 3)"], "E1404:")
call CheckLegacyAndVim9Failure(["call printf('%1$ud %1$d', 3)"], "E1404:")
call CheckLegacyAndVim9Failure(["call printf('%1$s %1$f', 3.0)"], "E1404:")
call CheckLegacyAndVim9Failure(["call printf('%1$*1$d %1$ld', 3)"], "E1404:")
call CheckLegacyAndVim9Failure(["call printf('%1$s %1$d', 3)"], "E1404:")
call CheckLegacyAndVim9Failure(["call printf('%1$p %1$d', 3)"], "E1404:")
call CheckLegacyAndVim9Failure(["call printf('%1$f %1$d', 3)"], "E1404:")
call CheckLegacyAndVim9Failure(["call printf('%1$lud %1$d', 3)"], "E1404:")
call CheckLegacyAndVim9Failure(["call printf('%1$llud %1$d', 3)"], "E1404:")
call CheckLegacyAndVim9Failure(["call printf('%1$lld %1$d', 3)"], "E1404:")
call CheckLegacyAndVim9Failure(["call printf('%1$s %1$d', 3)"], "E1404:")
call CheckLegacyAndVim9Failure(["call printf('%1$c %1$d', 3)"], "E1404:")
call CheckLegacyAndVim9Failure(["call printf('%1$ld %1$d', 3)"], "E1404:")
call CheckLegacyAndVim9Failure(["call printf('%1$.2$d', 3)"], "E1405:")
call CheckLegacyAndVim9Failure(["call printf('%01$d', 3)"], "E1405:")
call CheckLegacyAndVim9Failure(["call printf('%01$0d', 3)"], "E1405:")
call CheckLegacyAndVim9Failure(["call printf('%1$*2d', 3)"], "E1405:")
call CheckLegacyAndVim9Failure(["call printf('%1$*3.*2$d', 3)"], "E1405:")
call CheckLegacyAndVim9Failure(["call printf('%1$*3$.2$d', 3)"], "E1405:")
call CheckLegacyAndVim9Failure(["call printf('%1$*3$.*2d', 3)"], "E1405:")
call CheckLegacyAndVim9Failure(["call printf('%1$1$.5d', 5)"], "E1405:")
call CheckLegacyAndVim9Failure(["call printf('%1$5.1$d', 5)"], "E1405:")
call 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("str2float('inf')", printf('%1$s', 1.0 / 0.0))
call assert_equal("-str2float('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("str2float('nan')", printf('%1$s', 0.0 / 0.0))
call assert_equal("str2float('nan')", printf('%1$s', -0.0 / 0.0))
call assert_equal("str2float('nan')", printf('%1$S', 0.0 / 0.0))
call assert_equal("str2float('nan')", printf('%1$S', -0.0 / 0.0))
END
call CheckLegacyAndVim9Success(lines)
call CheckLegacyAndVim9Failure(['echo printf("%f", "a")'], 'E807:')
endfunc
func Test_printf_pos_errors()
call CheckLegacyAndVim9Failure(['echo printf("%1$d", {})'], 'E728:')
call CheckLegacyAndVim9Failure(['echo printf("%1$d", [])'], 'E745:')
call CheckLegacyAndVim9Failure(['echo printf("%1$d", 1, 2)'], 'E767:')
call CheckLegacyAndVim9Failure(['echo printf("%*d", 1)'], 'E766:')
call CheckLegacyAndVim9Failure(['echo printf("%1$s")'], 'E1403:')
call CheckLegacyAndVim9Failure(['echo printf("%1$d", 1.2)'], 'E805:')
call 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 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 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 CheckLegacyAndVim9Success(lines)
endfunc
" vim: shiftwidth=2 sts=2 expandtab

View File

@@ -1439,7 +1439,7 @@ describe('typval.c', function()
eq('3', tv_list_find_str(l, 2))
eq('3', tv_list_find_str(l, -3))
alloc_log:check({a.freed(alloc_log.null)})
alloc_log:check({a.freed(alloc_log.null), a.freed(alloc_log.null)})
end)
itp('returns string when used with VAR_STRING items', function()
local l = list('1', '2', '3', '4', '5')
@@ -1768,7 +1768,7 @@ describe('typval.c', function()
local s44 = check_emsg(function() return lib.tv_dict_get_string(d, 't', false) end,
nil)
eq('44.0', ffi.string(s44))
alloc_log:check({a.freed(alloc_log.null)})
alloc_log:check({a.freed(alloc_log.null), a.freed(alloc_log.null)})
end)
itp('allocates a string copy when requested', function()
local function tv_dict_get_string_alloc(d, key, emsg, is_float)
@@ -1779,7 +1779,7 @@ describe('typval.c', function()
if not emsg then
if s_ret then
if is_float then
alloc_log:check({a.freed(alloc_log.null), a.str(ret, s_ret)})
alloc_log:check({a.freed(alloc_log.null), a.freed(alloc_log.null), a.str(ret, s_ret)})
else
alloc_log:check({a.str(ret, s_ret)})
end
@@ -1810,7 +1810,7 @@ describe('typval.c', function()
local s_ret = (ret ~= nil) and ffi.string(ret) or nil
if not emsg then
if is_float then
alloc_log:check({a.freed(alloc_log.null)})
alloc_log:check({a.freed(alloc_log.null), a.freed(alloc_log.null)})
else
alloc_log:check({})
end
@@ -1856,7 +1856,7 @@ describe('typval.c', function()
local s_ret = (ret ~= nil) and ffi.string(ret) or nil
if not emsg then
if is_float then
alloc_log:check({a.freed(alloc_log.null)})
alloc_log:check({a.freed(alloc_log.null), a.freed(alloc_log.null)})
else
alloc_log:check({})
end
@@ -3018,7 +3018,7 @@ describe('typval.c', function()
if emsg then
alloc_log:clear()
elseif tv.v_type == lib.VAR_FLOAT then
alloc_log:check({a.freed(alloc_log.null)})
alloc_log:check({a.freed(alloc_log.null), a.freed(alloc_log.null)})
else
alloc_log:check({})
end

View File

@@ -140,37 +140,83 @@ describe('vim_strchr()', function()
end)
describe('vim_snprintf()', function()
itp('truncation', function()
local function check(expected, buf, bsize, fmt, ...)
eq(#expected, strings.vim_snprintf(buf, bsize, fmt, ...))
if bsize > 0 then
local actual = ffi.string(buf, math.min(#expected + 1, bsize))
eq(expected:sub(1, bsize - 1) .. '\0', actual)
end
local function a(expected, buf, bsize, fmt, ...)
eq(#expected, strings.vim_snprintf(buf, bsize, fmt, ...))
if bsize > 0 then
local actual = ffi.string(buf, math.min(#expected + 1, bsize))
eq(expected:sub(1, bsize - 1) .. '\0', actual)
end
end
local function i(n) return ffi.cast('int', n) end
local function l(n) return ffi.cast('long', n) end
local function u(n) return ffi.cast('unsigned', n) end
local function ll(n) return ffi.cast('long long', n) end
local function ul(n) return ffi.cast('unsigned long', n) end
local function ull(n) return ffi.cast('unsigned long long', n) end
itp('truncation', function()
for bsize = 0, 14 do
local buf = ffi.gc(strings.xmalloc(bsize), strings.xfree)
check('1234567', buf, bsize, '%d', ffi.cast('int', 1234567))
check('1234567', buf, bsize, '%ld', ffi.cast('long', 1234567))
check(' 1234567', buf, bsize, '%9ld', ffi.cast('long', 1234567))
check('1234567 ', buf, bsize, '%-9ld', ffi.cast('long', 1234567))
check('deadbeef', buf, bsize, '%x', ffi.cast('unsigned', 0xdeadbeef))
check('001100', buf, bsize, '%06b', ffi.cast('int', 12))
check('1.234000', buf, bsize, '%f', ffi.cast('double', 1.234))
check('1.234000e+00', buf, bsize, '%e', ffi.cast('double', 1.234))
check('nan', buf, bsize, '%f', ffi.cast('double', 0.0 / 0.0))
check('inf', buf, bsize, '%f', ffi.cast('double', 1.0 / 0.0))
check('-inf', buf, bsize, '%f', ffi.cast('double', -1.0 / 0.0))
check('-0.000000', buf, bsize, '%f', ffi.cast('double', -0.0))
check('漢語', buf, bsize, '%s', '漢語')
check(' 漢語', buf, bsize, '%8s', '漢語')
check('漢語 ', buf, bsize, '%-8s', '漢語')
check('', buf, bsize, '%.3s', '漢語')
check(' foo', buf, bsize, '%5S', 'foo')
check('%%%', buf, bsize, '%%%%%%')
check('0x87654321', buf, bsize, '%p', ffi.cast('char *', 0x87654321))
check('0x0087654321', buf, bsize, '%012p', ffi.cast('char *', 0x87654321))
a('1.00000001e7', buf, bsize, '%.8g', 10000000.1)
a('1234567', buf, bsize, '%d', i(1234567))
a('1234567', buf, bsize, '%ld', l(1234567))
a(' 1234567', buf, bsize, '%9ld', l(1234567))
a('1234567 ', buf, bsize, '%-9ld', l(1234567))
a('deadbeef', buf, bsize, '%x', u(0xdeadbeef))
a('001100', buf, bsize, '%06b', u(12))
a('one two', buf, bsize, '%s %s', 'one', 'two')
a('1.234000', buf, bsize, '%f', 1.234)
a('1.234000e+00', buf, bsize, '%e', 1.234)
a('nan', buf, bsize, '%f', 0.0 / 0.0)
a('inf', buf, bsize, '%f', 1.0 / 0.0)
a('-inf', buf, bsize, '%f', -1.0 / 0.0)
a('-0.000000', buf, bsize, '%f', -0.0)
a('漢語', buf, bsize, '%s', '漢語')
a(' 漢語', buf, bsize, '%8s', '漢語')
a('漢語 ', buf, bsize, '%-8s', '漢語')
a('', buf, bsize, '%.3s', '漢語')
a(' foo', buf, bsize, '%5S', 'foo')
a('%%%', buf, bsize, '%%%%%%')
a('0x87654321', buf, bsize, '%p', ffi.cast('char *', 0x87654321))
a('0x0087654321', buf, bsize, '%012p', ffi.cast('char *', 0x87654321))
end
end)
itp('positional arguments', function()
for bsize = 0, 24 do
local buf = ffi.gc(strings.xmalloc(bsize), strings.xfree)
a('1234567 ', buf, bsize, '%1$*2$ld', l(1234567), i(-9))
a('1234567 ', buf, bsize, '%1$*2$.*3$ld', l(1234567), i(-9), i(5))
a('1234567 ', buf, bsize, '%1$*3$.*2$ld', l(1234567), i(5), i(-9))
a('1234567 ', buf, bsize, '%3$*1$.*2$ld', i(-9), i(5), l(1234567))
a('1234567', buf, bsize, '%1$ld', l(1234567))
a(' 1234567', buf, bsize, '%1$*2$ld', l(1234567), i(9))
a('9 12345 7654321', buf, bsize, '%2$ld %1$d %3$lu', i(12345), l(9), ul(7654321))
a('9 1234567 7654321', buf, bsize, '%2$d %1$ld %3$lu', l(1234567), i(9), ul(7654321))
a('9 1234567 7654321', buf, bsize, '%2$d %1$lld %3$lu', ll(1234567), i(9), ul(7654321))
a('9 12345 7654321', buf, bsize, '%2$ld %1$u %3$lu', u(12345), l(9), ul(7654321))
a('9 1234567 7654321', buf, bsize, '%2$d %1$lu %3$lu', ul(1234567), i(9), ul(7654321))
a('9 1234567 7654321', buf, bsize, '%2$d %1$llu %3$lu', ull(1234567), i(9), ul(7654321))
a('9 1234567 7654321', buf, bsize, '%2$d %1$llu %3$lu', ull(1234567), i(9), ul(7654321))
a('9 deadbeef 7654321', buf, bsize, '%2$d %1$x %3$lu', u(0xdeadbeef), i(9), ul(7654321))
a('9 c 7654321', buf, bsize, '%2$ld %1$c %3$lu', i(('c'):byte()), l(9), ul(7654321))
a('9 hi 7654321', buf, bsize, '%2$ld %1$s %3$lu', 'hi', l(9), ul(7654321))
a('9 0.000000e+00 7654321', buf, bsize, '%2$ld %1$e %3$lu', 0.0, l(9), ul(7654321))
a('two one two', buf, bsize, '%2$s %1$s %2$s', 'one', 'two', 'three')
a('three one two', buf, bsize, '%3$s %1$s %2$s', 'one', 'two', 'three')
a('1234567', buf, bsize, '%1$d', i(1234567))
a('deadbeef', buf, bsize, '%1$x', u(0xdeadbeef))
a('001100', buf, bsize, '%1$0.*2$b', u(12), i(6))
a('one two', buf, bsize, '%1$s %2$s', 'one', 'two')
a('001100', buf, bsize, '%06b', u(12))
a('two one', buf, bsize, '%2$s %1$s', 'one', 'two')
a('1.234000', buf, bsize, '%1$f', 1.234)
a('1.234000e+00', buf, bsize, '%1$e', 1.234)
a('nan', buf, bsize, '%1$f', 0.0 / 0.0)
a('inf', buf, bsize, '%1$f', 1.0 / 0.0)
a('-inf', buf, bsize, '%1$f', -1.0 / 0.0)
a('-0.000000', buf, bsize, '%1$f', -0.0)
end
end)
end)