diff options
author | zeertzjq <zeertzjq@outlook.com> | 2023-08-15 19:16:19 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-08-15 19:16:19 +0800 |
commit | 842a47d6a4103a75e33c2c0023dbae5ad2c0f534 (patch) | |
tree | 9d1b92e4a36477bae40673233998ac8390a24a9b | |
parent | 7aad4643f9a6c2c3cc3033ae6dafef71036d3585 (diff) | |
download | rneovim-842a47d6a4103a75e33c2c0023dbae5ad2c0f534.tar.gz rneovim-842a47d6a4103a75e33c2c0023dbae5ad2c0f534.tar.bz2 rneovim-842a47d6a4103a75e33c2c0023dbae5ad2c0f534.zip |
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
https://github.com/vim/vim/commit/0c6181fec4c362eb9682d5af583341eb20cb1af5
Co-authored-by: Christ van Willegen <cvwillegen@gmail.com>
-rw-r--r-- | runtime/doc/builtin.txt | 111 | ||||
-rw-r--r-- | runtime/lua/vim/_meta/vimfn.lua | 111 | ||||
-rw-r--r-- | src/nvim/eval.lua | 111 | ||||
-rw-r--r-- | src/nvim/po/check.vim | 9 | ||||
-rw-r--r-- | src/nvim/strings.c | 789 | ||||
-rw-r--r-- | test/old/testdir/test_expr.vim | 2 | ||||
-rw-r--r-- | test/old/testdir/test_format.vim | 361 | ||||
-rw-r--r-- | test/unit/eval/typval_spec.lua | 12 | ||||
-rw-r--r-- | test/unit/strings_spec.lua | 100 |
9 files changed, 1529 insertions, 77 deletions
diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index d05f488ed5..7356f644a1 100644 --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -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|. diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua index d5d9229ffb..acf1b3a0f5 100644 --- a/runtime/lua/vim/_meta/vimfn.lua +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -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 diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 1e53014715..8a0d7575b3 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -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' } }, diff --git a/src/nvim/po/check.vim b/src/nvim/po/check.vim index 8752af663b..e67cb8c149 100644 --- a/src/nvim/po/check.vim +++ b/src/nvim/po/check.vim @@ -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. diff --git a/src/nvim/strings.c b/src/nvim/strings.c index 6fe2fd8ff3..2e603b8c9f 100644 --- a/src/nvim/strings.c +++ b/src/nvim/strings.c @@ -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. diff --git a/test/old/testdir/test_expr.vim b/test/old/testdir/test_expr.vim index 4cb8da8c74..bf1ba240eb 100644 --- a/test/old/testdir/test_expr.vim +++ b/test/old/testdir/test_expr.vim @@ -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)) diff --git a/test/old/testdir/test_format.vim b/test/old/testdir/test_format.vim new file mode 100644 index 0000000000..eae8af7b92 --- /dev/null +++ b/test/old/testdir/test_format.vim @@ -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 diff --git a/test/unit/eval/typval_spec.lua b/test/unit/eval/typval_spec.lua index a60700287f..6fb8ba5a1c 100644 --- a/test/unit/eval/typval_spec.lua +++ b/test/unit/eval/typval_spec.lua @@ -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 diff --git a/test/unit/strings_spec.lua b/test/unit/strings_spec.lua index bb5ea12edc..68790ea026 100644 --- a/test/unit/strings_spec.lua +++ b/test/unit/strings_spec.lua @@ -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) |