mirror of
https://github.com/vim/vim
synced 2025-03-16 14:57:52 +01:00
Problem: vim_strnchr() is strange and unnecessary (after v9.1.1009) Solution: Remove vim_strnchr() and use memchr() instead. Also remove a comment referencing an #if that is no longer present. vim_strnchr() is strange in several ways: - It's named like vim_strchr(), but unlike vim_strchr() it doesn't support finding a multibyte char. - Its logic is similar to vim_strbyte(), but unlike vim_strbyte() it uses char instead of char_u. - It takes a pointer as its size argument, which isn't convenient for all its callers. - It allows embedded NULs, unlike other "strn*" functions which stop when encountering a NUL byte. In comparison, memchr() also allows embedded NULs, and it converts bytes in the string to (unsigned char). closes: #16579 Signed-off-by: zeertzjq <zeertzjq@outlook.com> Signed-off-by: Christian Brabandt <cb@256bit.org>
4236 lines
88 KiB
C
4236 lines
88 KiB
C
/* vi:set ts=8 sts=4 sw=4 noet:
|
|
*
|
|
* VIM - Vi IMproved by Bram Moolenaar
|
|
*
|
|
* Do ":help uganda" in Vim to read copying and usage conditions.
|
|
* Do ":help credits" in Vim to see a list of people who contributed.
|
|
* See README.txt for an overview of the Vim source code.
|
|
*/
|
|
|
|
/*
|
|
* strings.c: string manipulation functions
|
|
*/
|
|
|
|
#define USING_FLOAT_STUFF
|
|
#include "vim.h"
|
|
|
|
/*
|
|
* Copy "string" into newly allocated memory.
|
|
*/
|
|
char_u *
|
|
vim_strsave(char_u *string)
|
|
{
|
|
char_u *p;
|
|
size_t len;
|
|
|
|
len = STRLEN(string) + 1;
|
|
p = alloc(len);
|
|
if (p != NULL)
|
|
mch_memmove(p, string, len);
|
|
return p;
|
|
}
|
|
|
|
/*
|
|
* Copy up to "len" bytes of "string" into newly allocated memory and
|
|
* terminate with a NUL.
|
|
* The allocated memory always has size "len + 1", also when "string" is
|
|
* shorter.
|
|
*/
|
|
char_u *
|
|
vim_strnsave(char_u *string, size_t len)
|
|
{
|
|
char_u *p;
|
|
|
|
p = alloc(len + 1);
|
|
if (p == NULL)
|
|
return NULL;
|
|
|
|
STRNCPY(p, string, len);
|
|
p[len] = NUL;
|
|
return p;
|
|
}
|
|
|
|
/*
|
|
* Same as vim_strsave(), but any characters found in esc_chars are preceded
|
|
* by a backslash.
|
|
*/
|
|
char_u *
|
|
vim_strsave_escaped(char_u *string, char_u *esc_chars)
|
|
{
|
|
return vim_strsave_escaped_ext(string, esc_chars, '\\', FALSE);
|
|
}
|
|
|
|
/*
|
|
* Same as vim_strsave_escaped(), but when "bsl" is TRUE also escape
|
|
* characters where rem_backslash() would remove the backslash.
|
|
* Escape the characters with "cc".
|
|
*/
|
|
char_u *
|
|
vim_strsave_escaped_ext(
|
|
char_u *string,
|
|
char_u *esc_chars,
|
|
int cc,
|
|
int bsl)
|
|
{
|
|
char_u *p;
|
|
char_u *p2;
|
|
char_u *escaped_string;
|
|
unsigned length;
|
|
int l;
|
|
|
|
// First count the number of backslashes required.
|
|
// Then allocate the memory and insert them.
|
|
length = 1; // count the trailing NUL
|
|
for (p = string; *p; p++)
|
|
{
|
|
if (has_mbyte && (l = (*mb_ptr2len)(p)) > 1)
|
|
{
|
|
length += l; // count a multibyte char
|
|
p += l - 1;
|
|
continue;
|
|
}
|
|
if (vim_strchr(esc_chars, *p) != NULL || (bsl && rem_backslash(p)))
|
|
++length; // count a backslash
|
|
++length; // count an ordinary char
|
|
}
|
|
escaped_string = alloc(length);
|
|
if (escaped_string == NULL)
|
|
return NULL;
|
|
p2 = escaped_string;
|
|
for (p = string; *p; p++)
|
|
{
|
|
if (has_mbyte && (l = (*mb_ptr2len)(p)) > 1)
|
|
{
|
|
mch_memmove(p2, p, (size_t)l);
|
|
p2 += l;
|
|
p += l - 1; // skip multibyte char
|
|
continue;
|
|
}
|
|
if (vim_strchr(esc_chars, *p) != NULL || (bsl && rem_backslash(p)))
|
|
*p2++ = cc;
|
|
*p2++ = *p;
|
|
}
|
|
*p2 = NUL;
|
|
return escaped_string;
|
|
}
|
|
|
|
/*
|
|
* Return TRUE when 'shell' has "csh" in the tail.
|
|
*/
|
|
int
|
|
csh_like_shell(void)
|
|
{
|
|
return (strstr((char *)gettail(p_sh), "csh") != NULL);
|
|
}
|
|
|
|
/*
|
|
* Return TRUE when 'shell' has "fish" in the tail.
|
|
*/
|
|
static int
|
|
fish_like_shell(void)
|
|
{
|
|
return (strstr((char *)gettail(p_sh), "fish") != NULL);
|
|
}
|
|
|
|
/*
|
|
* Escape "string" for use as a shell argument with system().
|
|
* This uses single quotes, except when we know we need to use double quotes
|
|
* (MS-DOS and MS-Windows not using PowerShell and without 'shellslash' set).
|
|
* PowerShell also uses a novel escaping for enclosed single quotes - double
|
|
* them up.
|
|
* Escape a newline, depending on the 'shell' option.
|
|
* When "do_special" is TRUE also replace "!", "%", "#" and things starting
|
|
* with "<" like "<cfile>".
|
|
* When "do_newline" is FALSE do not escape newline unless it is csh shell.
|
|
* Returns the result in allocated memory, NULL if we have run out.
|
|
*/
|
|
char_u *
|
|
vim_strsave_shellescape(char_u *string, int do_special, int do_newline)
|
|
{
|
|
unsigned length;
|
|
char_u *p;
|
|
char_u *d;
|
|
char_u *escaped_string;
|
|
size_t l;
|
|
int csh_like;
|
|
int fish_like;
|
|
char_u *shname;
|
|
int powershell;
|
|
# ifdef MSWIN
|
|
int double_quotes;
|
|
# endif
|
|
|
|
// Only csh and similar shells expand '!' within single quotes. For sh and
|
|
// the like we must not put a backslash before it, it will be taken
|
|
// literally. If do_special is set the '!' will be escaped twice.
|
|
// Csh also needs to have "\n" escaped twice when do_special is set.
|
|
csh_like = csh_like_shell();
|
|
|
|
// Fish shell uses '\' as an escape character within single quotes, so '\'
|
|
// itself must be escaped to get a literal '\'.
|
|
fish_like = fish_like_shell();
|
|
|
|
// PowerShell uses its own version for quoting single quotes
|
|
shname = gettail(p_sh);
|
|
powershell = strstr((char *)shname, "pwsh") != NULL;
|
|
# ifdef MSWIN
|
|
powershell = powershell || strstr((char *)shname, "powershell") != NULL;
|
|
// PowerShell only accepts single quotes so override shellslash.
|
|
double_quotes = !powershell && !p_ssl;
|
|
# endif
|
|
|
|
// First count the number of extra bytes required.
|
|
length = (unsigned)STRLEN(string) + 3; // two quotes and a trailing NUL
|
|
for (p = string; *p != NUL; MB_PTR_ADV(p))
|
|
{
|
|
# ifdef MSWIN
|
|
if (double_quotes)
|
|
{
|
|
if (*p == '"')
|
|
++length; // " -> ""
|
|
}
|
|
else
|
|
# endif
|
|
if (*p == '\'')
|
|
{
|
|
if (powershell)
|
|
length +=2; // ' => ''
|
|
else
|
|
length += 3; // ' => '\''
|
|
}
|
|
if ((*p == '\n' && (csh_like || do_newline))
|
|
|| (*p == '!' && (csh_like || do_special)))
|
|
{
|
|
++length; // insert backslash
|
|
if (csh_like && do_special)
|
|
++length; // insert backslash
|
|
}
|
|
if (do_special && find_cmdline_var(p, &l) >= 0)
|
|
{
|
|
++length; // insert backslash
|
|
p += l - 1;
|
|
}
|
|
if (*p == '\\' && fish_like)
|
|
++length; // insert backslash
|
|
}
|
|
|
|
// Allocate memory for the result and fill it.
|
|
escaped_string = alloc(length);
|
|
if (escaped_string != NULL)
|
|
{
|
|
d = escaped_string;
|
|
|
|
// add opening quote
|
|
# ifdef MSWIN
|
|
if (double_quotes)
|
|
*d++ = '"';
|
|
else
|
|
# endif
|
|
*d++ = '\'';
|
|
|
|
for (p = string; *p != NUL; )
|
|
{
|
|
# ifdef MSWIN
|
|
if (double_quotes)
|
|
{
|
|
if (*p == '"')
|
|
{
|
|
*d++ = '"';
|
|
*d++ = '"';
|
|
++p;
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
# endif
|
|
if (*p == '\'')
|
|
{
|
|
if (powershell)
|
|
{
|
|
*d++ = '\'';
|
|
*d++ = '\'';
|
|
}
|
|
else
|
|
{
|
|
*d++ = '\'';
|
|
*d++ = '\\';
|
|
*d++ = '\'';
|
|
*d++ = '\'';
|
|
}
|
|
++p;
|
|
continue;
|
|
}
|
|
if ((*p == '\n' && (csh_like || do_newline))
|
|
|| (*p == '!' && (csh_like || do_special)))
|
|
{
|
|
*d++ = '\\';
|
|
if (csh_like && do_special)
|
|
*d++ = '\\';
|
|
*d++ = *p++;
|
|
continue;
|
|
}
|
|
if (do_special && find_cmdline_var(p, &l) >= 0)
|
|
{
|
|
*d++ = '\\'; // insert backslash
|
|
memcpy(d, p, l); // copy the var
|
|
d += l;
|
|
p += l;
|
|
continue;
|
|
}
|
|
if (*p == '\\' && fish_like)
|
|
{
|
|
*d++ = '\\';
|
|
*d++ = *p++;
|
|
continue;
|
|
}
|
|
|
|
MB_COPY_CHAR(p, d);
|
|
}
|
|
|
|
// add terminating quote and finish with a NUL
|
|
# ifdef MSWIN
|
|
if (double_quotes)
|
|
*d++ = '"';
|
|
else
|
|
# endif
|
|
*d++ = '\'';
|
|
*d = NUL;
|
|
}
|
|
|
|
return escaped_string;
|
|
}
|
|
|
|
/*
|
|
* Like vim_strsave(), but make all characters uppercase.
|
|
* This uses ASCII lower-to-upper case translation, language independent.
|
|
*/
|
|
char_u *
|
|
vim_strsave_up(char_u *string)
|
|
{
|
|
char_u *p1;
|
|
|
|
p1 = vim_strsave(string);
|
|
vim_strup(p1);
|
|
return p1;
|
|
}
|
|
|
|
/*
|
|
* Like vim_strnsave(), but make all characters uppercase.
|
|
* This uses ASCII lower-to-upper case translation, language independent.
|
|
*/
|
|
char_u *
|
|
vim_strnsave_up(char_u *string, size_t len)
|
|
{
|
|
char_u *p1;
|
|
|
|
p1 = vim_strnsave(string, len);
|
|
vim_strup(p1);
|
|
return p1;
|
|
}
|
|
|
|
/*
|
|
* ASCII lower-to-upper case translation, language independent.
|
|
*/
|
|
void
|
|
vim_strup(
|
|
char_u *p)
|
|
{
|
|
char_u *p2;
|
|
int c;
|
|
|
|
if (p == NULL)
|
|
return;
|
|
|
|
p2 = p;
|
|
while ((c = *p2) != NUL)
|
|
*p2++ = (c < 'a' || c > 'z') ? c : (c - 0x20);
|
|
}
|
|
|
|
#if defined(FEAT_EVAL) || defined(FEAT_SPELL) || defined(PROTO)
|
|
/*
|
|
* Make string "s" all upper-case and return it in allocated memory.
|
|
* Handles multi-byte characters as well as possible.
|
|
* Returns NULL when out of memory.
|
|
*/
|
|
static char_u *
|
|
strup_save(char_u *orig)
|
|
{
|
|
char_u *p;
|
|
char_u *res;
|
|
|
|
res = p = vim_strsave(orig);
|
|
|
|
if (res != NULL)
|
|
while (*p != NUL)
|
|
{
|
|
int l;
|
|
|
|
if (enc_utf8)
|
|
{
|
|
int c, uc;
|
|
int newl;
|
|
char_u *s;
|
|
|
|
c = utf_ptr2char(p);
|
|
l = utf_ptr2len(p);
|
|
if (c == 0)
|
|
{
|
|
// overlong sequence, use only the first byte
|
|
c = *p;
|
|
l = 1;
|
|
}
|
|
uc = utf_toupper(c);
|
|
|
|
// Reallocate string when byte count changes. This is rare,
|
|
// thus it's OK to do another malloc()/free().
|
|
newl = utf_char2len(uc);
|
|
if (newl != l)
|
|
{
|
|
s = alloc(STRLEN(res) + 1 + newl - l);
|
|
if (s == NULL)
|
|
{
|
|
vim_free(res);
|
|
return NULL;
|
|
}
|
|
mch_memmove(s, res, p - res);
|
|
STRCPY(s + (p - res) + newl, p + l);
|
|
p = s + (p - res);
|
|
vim_free(res);
|
|
res = s;
|
|
}
|
|
|
|
utf_char2bytes(uc, p);
|
|
p += newl;
|
|
}
|
|
else if (has_mbyte && (l = (*mb_ptr2len)(p)) > 1)
|
|
p += l; // skip multi-byte character
|
|
else
|
|
{
|
|
*p = TOUPPER_LOC(*p); // note that toupper() can be a macro
|
|
p++;
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
/*
|
|
* Make string "s" all lower-case and return it in allocated memory.
|
|
* Handles multi-byte characters as well as possible.
|
|
* Returns NULL when out of memory.
|
|
*/
|
|
char_u *
|
|
strlow_save(char_u *orig)
|
|
{
|
|
char_u *p;
|
|
char_u *res;
|
|
|
|
res = p = vim_strsave(orig);
|
|
|
|
if (res != NULL)
|
|
while (*p != NUL)
|
|
{
|
|
int l;
|
|
|
|
if (enc_utf8)
|
|
{
|
|
int c, lc;
|
|
int newl;
|
|
char_u *s;
|
|
|
|
c = utf_ptr2char(p);
|
|
l = utf_ptr2len(p);
|
|
if (c == 0)
|
|
{
|
|
// overlong sequence, use only the first byte
|
|
c = *p;
|
|
l = 1;
|
|
}
|
|
lc = utf_tolower(c);
|
|
|
|
// Reallocate string when byte count changes. This is rare,
|
|
// thus it's OK to do another malloc()/free().
|
|
newl = utf_char2len(lc);
|
|
if (newl != l)
|
|
{
|
|
s = alloc(STRLEN(res) + 1 + newl - l);
|
|
if (s == NULL)
|
|
{
|
|
vim_free(res);
|
|
return NULL;
|
|
}
|
|
mch_memmove(s, res, p - res);
|
|
STRCPY(s + (p - res) + newl, p + l);
|
|
p = s + (p - res);
|
|
vim_free(res);
|
|
res = s;
|
|
}
|
|
|
|
utf_char2bytes(lc, p);
|
|
p += newl;
|
|
}
|
|
else if (has_mbyte && (l = (*mb_ptr2len)(p)) > 1)
|
|
p += l; // skip multi-byte character
|
|
else
|
|
{
|
|
*p = TOLOWER_LOC(*p); // note that tolower() can be a macro
|
|
p++;
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* delete spaces at the end of a string
|
|
*/
|
|
void
|
|
del_trailing_spaces(char_u *ptr)
|
|
{
|
|
char_u *q;
|
|
|
|
q = ptr + STRLEN(ptr);
|
|
while (--q > ptr && VIM_ISWHITE(q[0]) && q[-1] != '\\' && q[-1] != Ctrl_V)
|
|
*q = NUL;
|
|
}
|
|
|
|
/*
|
|
* Like strncpy(), but always terminate the result with one NUL.
|
|
* "to" must be "len + 1" long!
|
|
*/
|
|
void
|
|
vim_strncpy(char_u *to, char_u *from, size_t len)
|
|
{
|
|
STRNCPY(to, from, len);
|
|
to[len] = NUL;
|
|
}
|
|
|
|
/*
|
|
* Like strcat(), but make sure the result fits in "tosize" bytes and is
|
|
* always NUL terminated. "from" and "to" may overlap.
|
|
*/
|
|
void
|
|
vim_strcat(char_u *to, char_u *from, size_t tosize)
|
|
{
|
|
size_t tolen = STRLEN(to);
|
|
size_t fromlen = STRLEN(from);
|
|
|
|
if (tolen + fromlen + 1 > tosize)
|
|
{
|
|
mch_memmove(to + tolen, from, tosize - tolen - 1);
|
|
to[tosize - 1] = NUL;
|
|
}
|
|
else
|
|
mch_memmove(to + tolen, from, fromlen + 1);
|
|
}
|
|
|
|
/*
|
|
* A version of strlen() that has a maximum length.
|
|
*/
|
|
size_t
|
|
vim_strlen_maxlen(char *s, size_t maxlen)
|
|
{
|
|
size_t i;
|
|
for (i = 0; i < maxlen; ++i)
|
|
if (s[i] == NUL)
|
|
break;
|
|
return i;
|
|
}
|
|
|
|
#if (!defined(HAVE_STRCASECMP) && !defined(HAVE_STRICMP)) || defined(PROTO)
|
|
/*
|
|
* Compare two strings, ignoring case, using current locale.
|
|
* Doesn't work for multi-byte characters.
|
|
* return 0 for match, < 0 for smaller, > 0 for bigger
|
|
*/
|
|
int
|
|
vim_stricmp(char *s1, char *s2)
|
|
{
|
|
int i;
|
|
|
|
for (;;)
|
|
{
|
|
i = (int)TOLOWER_LOC(*s1) - (int)TOLOWER_LOC(*s2);
|
|
if (i != 0)
|
|
return i; // this character different
|
|
if (*s1 == NUL)
|
|
break; // strings match until NUL
|
|
++s1;
|
|
++s2;
|
|
}
|
|
return 0; // strings match
|
|
}
|
|
#endif
|
|
|
|
#if (!defined(HAVE_STRNCASECMP) && !defined(HAVE_STRNICMP)) || defined(PROTO)
|
|
/*
|
|
* Compare two strings, for length "len", ignoring case, using current locale.
|
|
* Doesn't work for multi-byte characters.
|
|
* return 0 for match, < 0 for smaller, > 0 for bigger
|
|
*/
|
|
int
|
|
vim_strnicmp(char *s1, char *s2, size_t len)
|
|
{
|
|
int i;
|
|
|
|
while (len > 0)
|
|
{
|
|
i = (int)TOLOWER_LOC(*s1) - (int)TOLOWER_LOC(*s2);
|
|
if (i != 0)
|
|
return i; // this character different
|
|
if (*s1 == NUL)
|
|
break; // strings match until NUL
|
|
++s1;
|
|
++s2;
|
|
--len;
|
|
}
|
|
return 0; // strings match
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Compare two ASCII strings, for length "len", ignoring case, ignoring locale
|
|
* (mostly matters for turkish locale where i I might be different).
|
|
* return 0 for match, < 0 for smaller, > 0 for bigger
|
|
*/
|
|
int
|
|
vim_strnicmp_asc(char *s1, char *s2, size_t len)
|
|
{
|
|
int i = 0;
|
|
int save_cmp_flags = cmp_flags;
|
|
|
|
cmp_flags |= CMP_KEEPASCII; // compare by ASCII value, ignoring locale
|
|
while (len > 0)
|
|
{
|
|
i = vim_tolower(*s1) - vim_tolower(*s2);
|
|
if (i != 0)
|
|
break; // this character is different
|
|
if (*s1 == NUL)
|
|
break; // strings match until NUL
|
|
++s1;
|
|
++s2;
|
|
--len;
|
|
}
|
|
cmp_flags = save_cmp_flags;
|
|
return i;
|
|
}
|
|
|
|
/*
|
|
* Search for first occurrence of "c" in "string".
|
|
* Version of strchr() that handles unsigned char strings with characters from
|
|
* 128 to 255 correctly. It also doesn't return a pointer to the NUL at the
|
|
* end of the string.
|
|
*/
|
|
char_u *
|
|
vim_strchr(char_u *string, int c)
|
|
{
|
|
char_u *p;
|
|
int b;
|
|
|
|
p = string;
|
|
if (enc_utf8 && c >= 0x80)
|
|
{
|
|
while (*p != NUL)
|
|
{
|
|
int l = utfc_ptr2len(p);
|
|
|
|
// Avoid matching an illegal byte here.
|
|
if (utf_ptr2char(p) == c && l > 1)
|
|
return p;
|
|
p += l;
|
|
}
|
|
return NULL;
|
|
}
|
|
if (enc_dbcs != 0 && c > 255)
|
|
{
|
|
int n2 = c & 0xff;
|
|
|
|
c = ((unsigned)c >> 8) & 0xff;
|
|
while ((b = *p) != NUL)
|
|
{
|
|
if (b == c && p[1] == n2)
|
|
return p;
|
|
p += (*mb_ptr2len)(p);
|
|
}
|
|
return NULL;
|
|
}
|
|
if (has_mbyte)
|
|
{
|
|
while ((b = *p) != NUL)
|
|
{
|
|
if (b == c)
|
|
return p;
|
|
p += (*mb_ptr2len)(p);
|
|
}
|
|
return NULL;
|
|
}
|
|
while ((b = *p) != NUL)
|
|
{
|
|
if (b == c)
|
|
return p;
|
|
++p;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Version of strchr() that only works for bytes and handles unsigned char
|
|
* strings with characters above 128 correctly. It also doesn't return a
|
|
* pointer to the NUL at the end of the string.
|
|
*/
|
|
char_u *
|
|
vim_strbyte(char_u *string, int c)
|
|
{
|
|
char_u *p = string;
|
|
|
|
while (*p != NUL)
|
|
{
|
|
if (*p == c)
|
|
return p;
|
|
++p;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Search for last occurrence of "c" in "string".
|
|
* Version of strrchr() that handles unsigned char strings with characters from
|
|
* 128 to 255 correctly. It also doesn't return a pointer to the NUL at the
|
|
* end of the string.
|
|
* Return NULL if not found.
|
|
* Does not handle multi-byte char for "c"!
|
|
*/
|
|
char_u *
|
|
vim_strrchr(char_u *string, int c)
|
|
{
|
|
char_u *retval = NULL;
|
|
char_u *p = string;
|
|
|
|
while (*p)
|
|
{
|
|
if (*p == c)
|
|
retval = p;
|
|
MB_PTR_ADV(p);
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
* Vim's version of strpbrk(), in case it's missing.
|
|
* Don't generate a prototype for this, causes problems when it's not used.
|
|
*/
|
|
#ifndef PROTO
|
|
# ifndef HAVE_STRPBRK
|
|
# ifdef vim_strpbrk
|
|
# undef vim_strpbrk
|
|
# endif
|
|
char_u *
|
|
vim_strpbrk(char_u *s, char_u *charset)
|
|
{
|
|
while (*s)
|
|
{
|
|
if (vim_strchr(charset, *s) != NULL)
|
|
return s;
|
|
MB_PTR_ADV(s);
|
|
}
|
|
return NULL;
|
|
}
|
|
# endif
|
|
#endif
|
|
|
|
/*
|
|
* Sort an array of strings.
|
|
*/
|
|
static int sort_compare(const void *s1, const void *s2);
|
|
|
|
static int
|
|
sort_compare(const void *s1, const void *s2)
|
|
{
|
|
return STRCMP(*(char **)s1, *(char **)s2);
|
|
}
|
|
|
|
void
|
|
sort_strings(
|
|
char_u **files,
|
|
int count)
|
|
{
|
|
qsort((void *)files, (size_t)count, sizeof(char_u *), sort_compare);
|
|
}
|
|
|
|
#if defined(FEAT_QUICKFIX) || defined(FEAT_SPELL) || defined(PROTO)
|
|
/*
|
|
* Return TRUE if string "s" contains a non-ASCII character (128 or higher).
|
|
* When "s" is NULL FALSE is returned.
|
|
*/
|
|
int
|
|
has_non_ascii(char_u *s)
|
|
{
|
|
char_u *p;
|
|
|
|
if (s != NULL)
|
|
for (p = s; *p != NUL; ++p)
|
|
if (*p >= 128)
|
|
return TRUE;
|
|
return FALSE;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Concatenate two strings and return the result in allocated memory.
|
|
* Returns NULL when out of memory.
|
|
*/
|
|
char_u *
|
|
concat_str(char_u *str1, char_u *str2)
|
|
{
|
|
char_u *dest;
|
|
size_t l = str1 == NULL ? 0 : STRLEN(str1);
|
|
|
|
dest = alloc(l + (str2 == NULL ? 0 : STRLEN(str2)) + 1L);
|
|
if (dest == NULL)
|
|
return NULL;
|
|
if (str1 == NULL)
|
|
*dest = NUL;
|
|
else
|
|
STRCPY(dest, str1);
|
|
if (str2 != NULL)
|
|
STRCPY(dest + l, str2);
|
|
return dest;
|
|
}
|
|
|
|
#if defined(FEAT_EVAL) || defined(FEAT_RIGHTLEFT) || defined(PROTO)
|
|
/*
|
|
* Reverse text into allocated memory.
|
|
* Returns the allocated string, NULL when out of memory.
|
|
*/
|
|
char_u *
|
|
reverse_text(char_u *s)
|
|
{
|
|
size_t len = STRLEN(s);
|
|
char_u *rev = alloc(len + 1);
|
|
if (rev == NULL)
|
|
return NULL;
|
|
|
|
for (size_t s_i = 0, rev_i = len; s_i < len; ++s_i)
|
|
{
|
|
if (has_mbyte)
|
|
{
|
|
int mb_len = (*mb_ptr2len)(s + s_i);
|
|
rev_i -= mb_len;
|
|
mch_memmove(rev + rev_i, s + s_i, mb_len);
|
|
s_i += mb_len - 1;
|
|
}
|
|
else
|
|
rev[--rev_i] = s[s_i];
|
|
}
|
|
rev[len] = NUL;
|
|
return rev;
|
|
}
|
|
#endif
|
|
|
|
#if defined(FEAT_EVAL) || defined(PROTO)
|
|
/*
|
|
* Return string "str" in ' quotes, doubling ' characters.
|
|
* If "str" is NULL an empty string is assumed.
|
|
* If "function" is TRUE make it function('string').
|
|
*/
|
|
char_u *
|
|
string_quote(char_u *str, int function)
|
|
{
|
|
unsigned len;
|
|
char_u *p, *r, *s;
|
|
|
|
len = (function ? 13 : 3);
|
|
if (str != NULL)
|
|
{
|
|
len += (unsigned)STRLEN(str);
|
|
for (p = str; *p != NUL; MB_PTR_ADV(p))
|
|
if (*p == '\'')
|
|
++len;
|
|
}
|
|
s = r = alloc(len);
|
|
if (r == NULL)
|
|
return NULL;
|
|
|
|
if (function)
|
|
{
|
|
STRCPY(r, "function('");
|
|
r += 10;
|
|
}
|
|
else
|
|
*r++ = '\'';
|
|
if (str != NULL)
|
|
for (p = str; *p != NUL; )
|
|
{
|
|
if (*p == '\'')
|
|
*r++ = '\'';
|
|
MB_COPY_CHAR(p, r);
|
|
}
|
|
*r++ = '\'';
|
|
if (function)
|
|
*r++ = ')';
|
|
*r++ = NUL;
|
|
return s;
|
|
}
|
|
|
|
/*
|
|
* Count the number of times "needle" occurs in string "haystack". Case is
|
|
* ignored if "ic" is TRUE.
|
|
*/
|
|
long
|
|
string_count(char_u *haystack, char_u *needle, int ic)
|
|
{
|
|
long n = 0;
|
|
char_u *p = haystack;
|
|
char_u *next;
|
|
|
|
if (p == NULL || needle == NULL || *needle == NUL)
|
|
return 0;
|
|
|
|
if (ic)
|
|
{
|
|
size_t len = STRLEN(needle);
|
|
|
|
while (*p != NUL)
|
|
{
|
|
if (MB_STRNICMP(p, needle, len) == 0)
|
|
{
|
|
++n;
|
|
p += len;
|
|
}
|
|
else
|
|
MB_PTR_ADV(p);
|
|
}
|
|
}
|
|
else
|
|
while ((next = (char_u *)strstr((char *)p, (char *)needle)) != NULL)
|
|
{
|
|
++n;
|
|
p = next + STRLEN(needle);
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
/*
|
|
* Make a typval_T of the first character of "input" and store it in "output".
|
|
* Return OK or FAIL.
|
|
*/
|
|
static int
|
|
copy_first_char_to_tv(char_u *input, typval_T *output)
|
|
{
|
|
char_u buf[MB_MAXBYTES + 1];
|
|
int len;
|
|
|
|
if (input == NULL || output == NULL)
|
|
return FAIL;
|
|
|
|
len = has_mbyte ? mb_ptr2len(input) : 1;
|
|
STRNCPY(buf, input, len);
|
|
buf[len] = NUL;
|
|
output->v_type = VAR_STRING;
|
|
output->vval.v_string = vim_strsave(buf);
|
|
|
|
return output->vval.v_string == NULL ? FAIL : OK;
|
|
}
|
|
|
|
/*
|
|
* Implementation of map() and filter() for a String. Apply "expr" to every
|
|
* character in string "str" and return the result in "rettv".
|
|
*/
|
|
void
|
|
string_filter_map(
|
|
char_u *str,
|
|
filtermap_T filtermap,
|
|
typval_T *expr,
|
|
typval_T *rettv)
|
|
{
|
|
char_u *p;
|
|
typval_T tv;
|
|
garray_T ga;
|
|
int len = 0;
|
|
int idx = 0;
|
|
int rem;
|
|
typval_T newtv;
|
|
funccall_T *fc;
|
|
|
|
rettv->v_type = VAR_STRING;
|
|
rettv->vval.v_string = NULL;
|
|
|
|
// set_vim_var_nr() doesn't set the type
|
|
set_vim_var_type(VV_KEY, VAR_NUMBER);
|
|
|
|
// Create one funccall_T for all eval_expr_typval() calls.
|
|
fc = eval_expr_get_funccal(expr, &newtv);
|
|
|
|
ga_init2(&ga, sizeof(char), 80);
|
|
for (p = str; *p != NUL; p += len)
|
|
{
|
|
if (copy_first_char_to_tv(p, &tv) == FAIL)
|
|
break;
|
|
len = (int)STRLEN(tv.vval.v_string);
|
|
|
|
set_vim_var_nr(VV_KEY, idx);
|
|
if (filter_map_one(&tv, expr, filtermap, fc, &newtv, &rem) == FAIL
|
|
|| did_emsg)
|
|
{
|
|
clear_tv(&newtv);
|
|
clear_tv(&tv);
|
|
break;
|
|
}
|
|
if (filtermap == FILTERMAP_MAP || filtermap == FILTERMAP_MAPNEW)
|
|
{
|
|
if (newtv.v_type != VAR_STRING)
|
|
{
|
|
clear_tv(&newtv);
|
|
clear_tv(&tv);
|
|
emsg(_(e_string_required));
|
|
break;
|
|
}
|
|
else
|
|
ga_concat(&ga, newtv.vval.v_string);
|
|
}
|
|
else if (filtermap == FILTERMAP_FOREACH || !rem)
|
|
ga_concat(&ga, tv.vval.v_string);
|
|
|
|
clear_tv(&newtv);
|
|
clear_tv(&tv);
|
|
|
|
++idx;
|
|
}
|
|
ga_append(&ga, NUL);
|
|
rettv->vval.v_string = ga.ga_data;
|
|
if (fc != NULL)
|
|
remove_funccal();
|
|
}
|
|
|
|
/*
|
|
* Implementation of reduce() for String "argvars[0]" using the function "expr"
|
|
* starting with the optional initial value "argvars[2]" and return the result
|
|
* in "rettv".
|
|
*/
|
|
void
|
|
string_reduce(
|
|
typval_T *argvars,
|
|
typval_T *expr,
|
|
typval_T *rettv)
|
|
{
|
|
char_u *p = tv_get_string(&argvars[0]);
|
|
int len;
|
|
typval_T argv[3];
|
|
int r;
|
|
int called_emsg_start = called_emsg;
|
|
funccall_T *fc;
|
|
|
|
if (argvars[2].v_type == VAR_UNKNOWN)
|
|
{
|
|
if (*p == NUL)
|
|
{
|
|
semsg(_(e_reduce_of_an_empty_str_with_no_initial_value), "String");
|
|
return;
|
|
}
|
|
if (copy_first_char_to_tv(p, rettv) == FAIL)
|
|
return;
|
|
p += STRLEN(rettv->vval.v_string);
|
|
}
|
|
else if (check_for_string_arg(argvars, 2) == FAIL)
|
|
return;
|
|
else
|
|
copy_tv(&argvars[2], rettv);
|
|
|
|
// Create one funccall_T for all eval_expr_typval() calls.
|
|
fc = eval_expr_get_funccal(expr, rettv);
|
|
|
|
for ( ; *p != NUL; p += len)
|
|
{
|
|
argv[0] = *rettv;
|
|
if (copy_first_char_to_tv(p, &argv[1]) == FAIL)
|
|
break;
|
|
len = (int)STRLEN(argv[1].vval.v_string);
|
|
|
|
r = eval_expr_typval(expr, TRUE, argv, 2, fc, rettv);
|
|
|
|
clear_tv(&argv[0]);
|
|
clear_tv(&argv[1]);
|
|
if (r == FAIL || called_emsg != called_emsg_start)
|
|
return;
|
|
}
|
|
|
|
if (fc != NULL)
|
|
remove_funccal();
|
|
}
|
|
|
|
/*
|
|
* Implementation of "byteidx()" and "byteidxcomp()" functions
|
|
*/
|
|
static void
|
|
byteidx_common(typval_T *argvars, typval_T *rettv, int comp)
|
|
{
|
|
rettv->vval.v_number = -1;
|
|
|
|
if (in_vim9script()
|
|
&& (check_for_string_arg(argvars, 0) == FAIL
|
|
|| check_for_number_arg(argvars, 1) == FAIL
|
|
|| check_for_opt_bool_arg(argvars, 2) == FAIL))
|
|
return;
|
|
|
|
char_u *str = tv_get_string_chk(&argvars[0]);
|
|
varnumber_T idx = tv_get_number_chk(&argvars[1], NULL);
|
|
if (str == NULL || idx < 0)
|
|
return;
|
|
|
|
varnumber_T utf16idx = FALSE;
|
|
if (argvars[2].v_type != VAR_UNKNOWN)
|
|
{
|
|
int error = FALSE;
|
|
utf16idx = tv_get_bool_chk(&argvars[2], &error);
|
|
if (error)
|
|
return;
|
|
if (utf16idx < 0 || utf16idx > 1)
|
|
{
|
|
semsg(_(e_using_number_as_bool_nr), utf16idx);
|
|
return;
|
|
}
|
|
}
|
|
|
|
int (*ptr2len)(char_u *);
|
|
if (enc_utf8 && comp)
|
|
ptr2len = utf_ptr2len;
|
|
else
|
|
ptr2len = mb_ptr2len;
|
|
|
|
char_u *t = str;
|
|
for ( ; idx > 0; idx--)
|
|
{
|
|
if (*t == NUL) // EOL reached
|
|
return;
|
|
if (utf16idx)
|
|
{
|
|
int clen = ptr2len(t);
|
|
int c = (clen > 1) ? utf_ptr2char(t) : *t;
|
|
if (c > 0xFFFF)
|
|
idx--;
|
|
}
|
|
if (idx > 0)
|
|
t += ptr2len(t);
|
|
}
|
|
rettv->vval.v_number = (varnumber_T)(t - str);
|
|
}
|
|
|
|
/*
|
|
* "byteidx()" function
|
|
*/
|
|
void
|
|
f_byteidx(typval_T *argvars, typval_T *rettv)
|
|
{
|
|
byteidx_common(argvars, rettv, FALSE);
|
|
}
|
|
|
|
/*
|
|
* "byteidxcomp()" function
|
|
*/
|
|
void
|
|
f_byteidxcomp(typval_T *argvars, typval_T *rettv)
|
|
{
|
|
byteidx_common(argvars, rettv, TRUE);
|
|
}
|
|
|
|
/*
|
|
* "charidx()" function
|
|
*/
|
|
void
|
|
f_charidx(typval_T *argvars, typval_T *rettv)
|
|
{
|
|
rettv->vval.v_number = -1;
|
|
|
|
if (check_for_string_arg(argvars, 0) == FAIL
|
|
|| check_for_number_arg(argvars, 1) == FAIL
|
|
|| check_for_opt_bool_arg(argvars, 2) == FAIL
|
|
|| (argvars[2].v_type != VAR_UNKNOWN
|
|
&& check_for_opt_bool_arg(argvars, 3) == FAIL))
|
|
return;
|
|
|
|
char_u *str = tv_get_string_chk(&argvars[0]);
|
|
varnumber_T idx = tv_get_number_chk(&argvars[1], NULL);
|
|
if (str == NULL || idx < 0)
|
|
return;
|
|
|
|
varnumber_T countcc = FALSE;
|
|
varnumber_T utf16idx = FALSE;
|
|
if (argvars[2].v_type != VAR_UNKNOWN)
|
|
{
|
|
countcc = tv_get_bool(&argvars[2]);
|
|
if (argvars[3].v_type != VAR_UNKNOWN)
|
|
utf16idx = tv_get_bool(&argvars[3]);
|
|
}
|
|
|
|
int (*ptr2len)(char_u *);
|
|
if (enc_utf8 && countcc)
|
|
ptr2len = utf_ptr2len;
|
|
else
|
|
ptr2len = mb_ptr2len;
|
|
|
|
char_u *p;
|
|
int len;
|
|
for (p = str, len = 0; utf16idx ? idx >= 0 : p <= str + idx; len++)
|
|
{
|
|
if (*p == NUL)
|
|
{
|
|
// If the index is exactly the number of bytes or utf-16 code units
|
|
// in the string then return the length of the string in
|
|
// characters.
|
|
if (utf16idx ? (idx == 0) : (p == (str + idx)))
|
|
rettv->vval.v_number = len;
|
|
return;
|
|
}
|
|
if (utf16idx)
|
|
{
|
|
idx--;
|
|
int clen = ptr2len(p);
|
|
int c = (clen > 1) ? utf_ptr2char(p) : *p;
|
|
if (c > 0xFFFF)
|
|
idx--;
|
|
}
|
|
p += ptr2len(p);
|
|
}
|
|
|
|
rettv->vval.v_number = len > 0 ? len - 1 : 0;
|
|
}
|
|
|
|
/*
|
|
* Convert the string "str", from encoding "from" to encoding "to".
|
|
*/
|
|
static char_u *
|
|
convert_string(char_u *str, char_u *from, char_u *to)
|
|
{
|
|
vimconv_T vimconv;
|
|
|
|
vimconv.vc_type = CONV_NONE;
|
|
if (convert_setup(&vimconv, from, to) == FAIL)
|
|
return NULL;
|
|
vimconv.vc_fail = TRUE;
|
|
if (vimconv.vc_type == CONV_NONE)
|
|
str = vim_strsave(str);
|
|
else
|
|
str = string_convert(&vimconv, str, NULL);
|
|
convert_setup(&vimconv, NULL, NULL);
|
|
|
|
return str;
|
|
}
|
|
|
|
/*
|
|
* Add the bytes from "str" to "blob".
|
|
*/
|
|
static void
|
|
blob_from_string(char_u *str, blob_T *blob)
|
|
{
|
|
size_t len = STRLEN(str);
|
|
|
|
for (size_t i = 0; i < len; i++)
|
|
{
|
|
int ch = str[i];
|
|
|
|
if (str[i] == NL)
|
|
// Translate newlines in the string to NUL character
|
|
ch = NUL;
|
|
|
|
ga_append(&blob->bv_ga, ch);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Return a string created from the bytes in blob starting at "start_idx".
|
|
* A NL character in the blob indicates end of string.
|
|
* A NUL character in the blob is translated to a NL.
|
|
* On return, "start_idx" points to next byte to process in blob.
|
|
*/
|
|
static char_u *
|
|
string_from_blob(blob_T *blob, long *start_idx)
|
|
{
|
|
garray_T str_ga;
|
|
long blen;
|
|
int idx;
|
|
|
|
ga_init2(&str_ga, sizeof(char), 80);
|
|
|
|
blen = blob_len(blob);
|
|
|
|
for (idx = *start_idx; idx < blen; idx++)
|
|
{
|
|
char_u byte = (char_u)blob_get(blob, idx);
|
|
if (byte == NL)
|
|
{
|
|
idx++;
|
|
break;
|
|
}
|
|
|
|
if (byte == NUL)
|
|
byte = NL;
|
|
|
|
ga_append(&str_ga, byte);
|
|
}
|
|
|
|
ga_append(&str_ga, NUL);
|
|
|
|
char_u *ret_str = vim_strsave(str_ga.ga_data);
|
|
*start_idx = idx;
|
|
|
|
ga_clear(&str_ga);
|
|
return ret_str;
|
|
}
|
|
|
|
/*
|
|
* "blob2str()" function
|
|
* Converts a blob to a string, ensuring valid UTF-8 encoding.
|
|
*/
|
|
void
|
|
f_blob2str(typval_T *argvars, typval_T *rettv)
|
|
{
|
|
blob_T *blob;
|
|
int blen;
|
|
long idx;
|
|
int utf8_inuse = FALSE;
|
|
|
|
if (check_for_blob_arg(argvars, 0) == FAIL
|
|
|| check_for_opt_dict_arg(argvars, 1) == FAIL)
|
|
return;
|
|
|
|
if (rettv_list_alloc(rettv) == FAIL)
|
|
return;
|
|
|
|
blob = argvars->vval.v_blob;
|
|
if (blob == NULL)
|
|
return;
|
|
blen = blob_len(blob);
|
|
|
|
char_u *from_encoding = NULL;
|
|
if (argvars[1].v_type != VAR_UNKNOWN)
|
|
{
|
|
dict_T *d = argvars[1].vval.v_dict;
|
|
if (d != NULL)
|
|
{
|
|
char_u *enc = dict_get_string(d, "encoding", FALSE);
|
|
if (enc != NULL)
|
|
from_encoding = enc_canonize(enc_skip(enc));
|
|
}
|
|
}
|
|
|
|
if (STRCMP(p_enc, "utf-8") == 0 || STRCMP(p_enc, "utf8") == 0)
|
|
utf8_inuse = TRUE;
|
|
|
|
idx = 0;
|
|
while (idx < blen)
|
|
{
|
|
char_u *str;
|
|
char_u *converted_str;
|
|
|
|
str = string_from_blob(blob, &idx);
|
|
if (str == NULL)
|
|
break;
|
|
|
|
converted_str = str;
|
|
if (from_encoding != NULL)
|
|
{
|
|
converted_str = convert_string(str, from_encoding, p_enc);
|
|
vim_free(str);
|
|
if (converted_str == NULL)
|
|
{
|
|
semsg(_(e_str_encoding_failed), "from", from_encoding);
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
if (utf8_inuse)
|
|
{
|
|
if (!utf_valid_string(converted_str, NULL))
|
|
{
|
|
semsg(_(e_str_encoding_failed), "from", p_enc);
|
|
vim_free(converted_str);
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
int ret = list_append_string(rettv->vval.v_list, converted_str, -1);
|
|
vim_free(converted_str);
|
|
if (ret == FAIL)
|
|
break;
|
|
}
|
|
|
|
done:
|
|
vim_free(from_encoding);
|
|
}
|
|
|
|
/*
|
|
* "str2blob()" function
|
|
*/
|
|
void
|
|
f_str2blob(typval_T *argvars, typval_T *rettv)
|
|
{
|
|
blob_T *blob;
|
|
list_T *list;
|
|
listitem_T *li;
|
|
|
|
if (check_for_list_arg(argvars, 0) == FAIL
|
|
|| check_for_opt_dict_arg(argvars, 1) == FAIL)
|
|
return;
|
|
|
|
if (rettv_blob_alloc(rettv) == FAIL)
|
|
return;
|
|
|
|
blob = rettv->vval.v_blob;
|
|
|
|
list = argvars[0].vval.v_list;
|
|
if (list == NULL)
|
|
return;
|
|
|
|
char_u *to_encoding = NULL;
|
|
if (argvars[1].v_type != VAR_UNKNOWN)
|
|
{
|
|
dict_T *d = argvars[1].vval.v_dict;
|
|
if (d != NULL)
|
|
{
|
|
char_u *enc = dict_get_string(d, "encoding", FALSE);
|
|
if (enc != NULL)
|
|
to_encoding = enc_canonize(enc_skip(enc));
|
|
}
|
|
}
|
|
|
|
FOR_ALL_LIST_ITEMS(list, li)
|
|
{
|
|
if (li->li_tv.v_type != VAR_STRING)
|
|
continue;
|
|
|
|
char_u *str = li->li_tv.vval.v_string;
|
|
|
|
if (str == NULL)
|
|
continue;
|
|
|
|
if (to_encoding != NULL)
|
|
{
|
|
str = convert_string(str, p_enc, to_encoding);
|
|
if (str == NULL)
|
|
{
|
|
semsg(_(e_str_encoding_failed), "to", to_encoding);
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
if (li != list->lv_first)
|
|
// Each list string item is separated by a newline in the blob
|
|
ga_append(&blob->bv_ga, NL);
|
|
|
|
blob_from_string(str, blob);
|
|
|
|
if (to_encoding != NULL)
|
|
vim_free(str);
|
|
}
|
|
|
|
done:
|
|
if (to_encoding != NULL)
|
|
vim_free(to_encoding);
|
|
}
|
|
|
|
/*
|
|
* "str2list()" function
|
|
*/
|
|
void
|
|
f_str2list(typval_T *argvars, typval_T *rettv)
|
|
{
|
|
char_u *p;
|
|
int utf8 = FALSE;
|
|
|
|
if (rettv_list_alloc(rettv) == FAIL)
|
|
return;
|
|
|
|
if (in_vim9script()
|
|
&& (check_for_string_arg(argvars, 0) == FAIL
|
|
|| check_for_opt_bool_arg(argvars, 1) == FAIL))
|
|
return;
|
|
|
|
if (argvars[1].v_type != VAR_UNKNOWN)
|
|
utf8 = (int)tv_get_bool_chk(&argvars[1], NULL);
|
|
|
|
p = tv_get_string(&argvars[0]);
|
|
|
|
if (has_mbyte || utf8)
|
|
{
|
|
int (*ptr2len)(char_u *);
|
|
int (*ptr2char)(char_u *);
|
|
|
|
if (utf8 || enc_utf8)
|
|
{
|
|
ptr2len = utf_ptr2len;
|
|
ptr2char = utf_ptr2char;
|
|
}
|
|
else
|
|
{
|
|
ptr2len = mb_ptr2len;
|
|
ptr2char = mb_ptr2char;
|
|
}
|
|
|
|
for ( ; *p != NUL; p += (*ptr2len)(p))
|
|
list_append_number(rettv->vval.v_list, (*ptr2char)(p));
|
|
}
|
|
else
|
|
for ( ; *p != NUL; ++p)
|
|
list_append_number(rettv->vval.v_list, *p);
|
|
}
|
|
|
|
/*
|
|
* "str2nr()" function
|
|
*/
|
|
void
|
|
f_str2nr(typval_T *argvars, typval_T *rettv)
|
|
{
|
|
int base = 10;
|
|
char_u *p;
|
|
varnumber_T n;
|
|
int what = 0;
|
|
int isneg;
|
|
|
|
if (in_vim9script()
|
|
&& (check_for_string_arg(argvars, 0) == FAIL
|
|
|| check_for_opt_number_arg(argvars, 1) == FAIL
|
|
|| (argvars[1].v_type != VAR_UNKNOWN
|
|
&& check_for_opt_bool_arg(argvars, 2) == FAIL)))
|
|
return;
|
|
|
|
if (argvars[1].v_type != VAR_UNKNOWN)
|
|
{
|
|
base = (int)tv_get_number(&argvars[1]);
|
|
if (base != 2 && base != 8 && base != 10 && base != 16)
|
|
{
|
|
emsg(_(e_invalid_argument));
|
|
return;
|
|
}
|
|
if (argvars[2].v_type != VAR_UNKNOWN && tv_get_bool(&argvars[2]))
|
|
what |= STR2NR_QUOTE;
|
|
}
|
|
|
|
p = skipwhite(tv_get_string_strict(&argvars[0]));
|
|
isneg = (*p == '-');
|
|
if (*p == '+' || *p == '-')
|
|
p = skipwhite(p + 1);
|
|
switch (base)
|
|
{
|
|
case 2: what |= STR2NR_BIN + STR2NR_FORCE; break;
|
|
case 8: what |= STR2NR_OCT + STR2NR_OOCT + STR2NR_FORCE; break;
|
|
case 16: what |= STR2NR_HEX + STR2NR_FORCE; break;
|
|
}
|
|
vim_str2nr(p, NULL, NULL, what, &n, NULL, 0, FALSE, NULL);
|
|
// Text after the number is silently ignored.
|
|
if (isneg)
|
|
rettv->vval.v_number = -n;
|
|
else
|
|
rettv->vval.v_number = n;
|
|
|
|
}
|
|
|
|
/*
|
|
* "strgetchar()" function
|
|
*/
|
|
void
|
|
f_strgetchar(typval_T *argvars, typval_T *rettv)
|
|
{
|
|
char_u *str;
|
|
int len;
|
|
int error = FALSE;
|
|
int charidx;
|
|
int byteidx = 0;
|
|
|
|
rettv->vval.v_number = -1;
|
|
|
|
if (in_vim9script()
|
|
&& (check_for_string_arg(argvars, 0) == FAIL
|
|
|| check_for_number_arg(argvars, 1) == FAIL))
|
|
return;
|
|
|
|
str = tv_get_string_chk(&argvars[0]);
|
|
if (str == NULL)
|
|
return;
|
|
len = (int)STRLEN(str);
|
|
charidx = (int)tv_get_number_chk(&argvars[1], &error);
|
|
if (error)
|
|
return;
|
|
|
|
while (charidx >= 0 && byteidx < len)
|
|
{
|
|
if (charidx == 0)
|
|
{
|
|
rettv->vval.v_number = mb_ptr2char(str + byteidx);
|
|
break;
|
|
}
|
|
--charidx;
|
|
byteidx += MB_CPTR2LEN(str + byteidx);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* "stridx()" function
|
|
*/
|
|
void
|
|
f_stridx(typval_T *argvars, typval_T *rettv)
|
|
{
|
|
char_u buf[NUMBUFLEN];
|
|
char_u *needle;
|
|
char_u *haystack;
|
|
char_u *save_haystack;
|
|
char_u *pos;
|
|
int start_idx;
|
|
|
|
if (in_vim9script()
|
|
&& (check_for_string_arg(argvars, 0) == FAIL
|
|
|| check_for_string_arg(argvars, 1) == FAIL
|
|
|| check_for_opt_number_arg(argvars, 2) == FAIL))
|
|
return;
|
|
|
|
needle = tv_get_string_chk(&argvars[1]);
|
|
save_haystack = haystack = tv_get_string_buf_chk(&argvars[0], buf);
|
|
rettv->vval.v_number = -1;
|
|
if (needle == NULL || haystack == NULL)
|
|
return; // type error; errmsg already given
|
|
|
|
if (argvars[2].v_type != VAR_UNKNOWN)
|
|
{
|
|
int error = FALSE;
|
|
|
|
start_idx = (int)tv_get_number_chk(&argvars[2], &error);
|
|
if (error || start_idx >= (int)STRLEN(haystack))
|
|
return;
|
|
if (start_idx >= 0)
|
|
haystack += start_idx;
|
|
}
|
|
|
|
pos = (char_u *)strstr((char *)haystack, (char *)needle);
|
|
if (pos != NULL)
|
|
rettv->vval.v_number = (varnumber_T)(pos - save_haystack);
|
|
}
|
|
|
|
/*
|
|
* "string()" function
|
|
*/
|
|
void
|
|
f_string(typval_T *argvars, typval_T *rettv)
|
|
{
|
|
char_u *tofree;
|
|
char_u numbuf[NUMBUFLEN];
|
|
|
|
rettv->v_type = VAR_STRING;
|
|
rettv->vval.v_string = tv2string(&argvars[0], &tofree, numbuf,
|
|
get_copyID());
|
|
// Make a copy if we have a value but it's not in allocated memory.
|
|
if (rettv->vval.v_string != NULL && tofree == NULL)
|
|
rettv->vval.v_string = vim_strsave(rettv->vval.v_string);
|
|
}
|
|
|
|
/*
|
|
* "strlen()" function
|
|
*/
|
|
void
|
|
f_strlen(typval_T *argvars, typval_T *rettv)
|
|
{
|
|
if (in_vim9script()
|
|
&& check_for_string_or_number_arg(argvars, 0) == FAIL)
|
|
return;
|
|
|
|
rettv->vval.v_number = (varnumber_T)(STRLEN(
|
|
tv_get_string(&argvars[0])));
|
|
}
|
|
|
|
static void
|
|
strchar_common(typval_T *argvars, typval_T *rettv, int skipcc)
|
|
{
|
|
char_u *s = tv_get_string(&argvars[0]);
|
|
varnumber_T len = 0;
|
|
int (*func_mb_ptr2char_adv)(char_u **pp);
|
|
|
|
func_mb_ptr2char_adv = skipcc ? mb_ptr2char_adv : mb_cptr2char_adv;
|
|
while (*s != NUL)
|
|
{
|
|
func_mb_ptr2char_adv(&s);
|
|
++len;
|
|
}
|
|
rettv->vval.v_number = len;
|
|
}
|
|
|
|
/*
|
|
* "strcharlen()" function
|
|
*/
|
|
void
|
|
f_strcharlen(typval_T *argvars, typval_T *rettv)
|
|
{
|
|
if (in_vim9script()
|
|
&& check_for_string_or_number_arg(argvars, 0) == FAIL)
|
|
return;
|
|
|
|
strchar_common(argvars, rettv, TRUE);
|
|
}
|
|
|
|
/*
|
|
* "strchars()" function
|
|
*/
|
|
void
|
|
f_strchars(typval_T *argvars, typval_T *rettv)
|
|
{
|
|
varnumber_T skipcc = FALSE;
|
|
|
|
if (in_vim9script()
|
|
&& (check_for_string_arg(argvars, 0) == FAIL
|
|
|| check_for_opt_bool_arg(argvars, 1) == FAIL))
|
|
return;
|
|
|
|
if (argvars[1].v_type != VAR_UNKNOWN)
|
|
{
|
|
int error = FALSE;
|
|
skipcc = tv_get_bool_chk(&argvars[1], &error);
|
|
if (error)
|
|
return;
|
|
if (skipcc < 0 || skipcc > 1)
|
|
{
|
|
semsg(_(e_using_number_as_bool_nr), skipcc);
|
|
return;
|
|
}
|
|
}
|
|
|
|
strchar_common(argvars, rettv, skipcc);
|
|
}
|
|
|
|
/*
|
|
* "strutf16len()" function
|
|
*/
|
|
void
|
|
f_strutf16len(typval_T *argvars, typval_T *rettv)
|
|
{
|
|
rettv->vval.v_number = -1;
|
|
|
|
if (check_for_string_arg(argvars, 0) == FAIL
|
|
|| check_for_opt_bool_arg(argvars, 1) == FAIL)
|
|
return;
|
|
|
|
varnumber_T countcc = FALSE;
|
|
if (argvars[1].v_type != VAR_UNKNOWN)
|
|
countcc = tv_get_bool(&argvars[1]);
|
|
|
|
char_u *s = tv_get_string(&argvars[0]);
|
|
varnumber_T len = 0;
|
|
int (*func_mb_ptr2char_adv)(char_u **pp);
|
|
int ch;
|
|
|
|
func_mb_ptr2char_adv = countcc ? mb_cptr2char_adv : mb_ptr2char_adv;
|
|
while (*s != NUL)
|
|
{
|
|
ch = func_mb_ptr2char_adv(&s);
|
|
if (ch > 0xFFFF)
|
|
++len;
|
|
++len;
|
|
}
|
|
rettv->vval.v_number = len;
|
|
}
|
|
|
|
/*
|
|
* "strdisplaywidth()" function
|
|
*/
|
|
void
|
|
f_strdisplaywidth(typval_T *argvars, typval_T *rettv)
|
|
{
|
|
char_u *s;
|
|
int col = 0;
|
|
|
|
rettv->vval.v_number = -1;
|
|
|
|
if (in_vim9script()
|
|
&& (check_for_string_arg(argvars, 0) == FAIL
|
|
|| check_for_opt_number_arg(argvars, 1) == FAIL))
|
|
return;
|
|
|
|
s = tv_get_string(&argvars[0]);
|
|
if (argvars[1].v_type != VAR_UNKNOWN)
|
|
col = (int)tv_get_number(&argvars[1]);
|
|
|
|
rettv->vval.v_number = (varnumber_T)(linetabsize_col(col, s) - col);
|
|
}
|
|
|
|
/*
|
|
* "strwidth()" function
|
|
*/
|
|
void
|
|
f_strwidth(typval_T *argvars, typval_T *rettv)
|
|
{
|
|
char_u *s;
|
|
|
|
if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
|
|
return;
|
|
|
|
s = tv_get_string_strict(&argvars[0]);
|
|
rettv->vval.v_number = (varnumber_T)(mb_string2cells(s, -1));
|
|
}
|
|
|
|
/*
|
|
* "strcharpart()" function
|
|
*/
|
|
void
|
|
f_strcharpart(typval_T *argvars, typval_T *rettv)
|
|
{
|
|
char_u *p;
|
|
int nchar;
|
|
int nbyte = 0;
|
|
int charlen;
|
|
int skipcc = FALSE;
|
|
int len = 0;
|
|
int slen;
|
|
int error = FALSE;
|
|
|
|
if (in_vim9script()
|
|
&& (check_for_string_arg(argvars, 0) == FAIL
|
|
|| check_for_number_arg(argvars, 1) == FAIL
|
|
|| check_for_opt_number_arg(argvars, 2) == FAIL
|
|
|| (argvars[2].v_type != VAR_UNKNOWN
|
|
&& check_for_opt_bool_arg(argvars, 3) == FAIL)))
|
|
return;
|
|
|
|
p = tv_get_string(&argvars[0]);
|
|
slen = (int)STRLEN(p);
|
|
|
|
nchar = (int)tv_get_number_chk(&argvars[1], &error);
|
|
if (!error)
|
|
{
|
|
if (argvars[2].v_type != VAR_UNKNOWN
|
|
&& argvars[3].v_type != VAR_UNKNOWN)
|
|
{
|
|
skipcc = tv_get_bool_chk(&argvars[3], &error);
|
|
if (error)
|
|
return;
|
|
if (skipcc < 0 || skipcc > 1)
|
|
{
|
|
semsg(_(e_using_number_as_bool_nr), skipcc);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (nchar > 0)
|
|
while (nchar > 0 && nbyte < slen)
|
|
{
|
|
if (skipcc)
|
|
nbyte += mb_ptr2len(p + nbyte);
|
|
else
|
|
nbyte += MB_CPTR2LEN(p + nbyte);
|
|
--nchar;
|
|
}
|
|
else
|
|
nbyte = nchar;
|
|
if (argvars[2].v_type != VAR_UNKNOWN)
|
|
{
|
|
charlen = (int)tv_get_number(&argvars[2]);
|
|
while (charlen > 0 && nbyte + len < slen)
|
|
{
|
|
int off = nbyte + len;
|
|
|
|
if (off < 0)
|
|
len += 1;
|
|
else
|
|
{
|
|
if (skipcc)
|
|
len += mb_ptr2len(p + off);
|
|
else
|
|
len += MB_CPTR2LEN(p + off);
|
|
}
|
|
--charlen;
|
|
}
|
|
}
|
|
else
|
|
len = slen - nbyte; // default: all bytes that are available.
|
|
}
|
|
|
|
// Only return the overlap between the specified part and the actual
|
|
// string.
|
|
if (nbyte < 0)
|
|
{
|
|
len += nbyte;
|
|
nbyte = 0;
|
|
}
|
|
else if (nbyte > slen)
|
|
nbyte = slen;
|
|
if (len < 0)
|
|
len = 0;
|
|
else if (nbyte + len > slen)
|
|
len = slen - nbyte;
|
|
|
|
rettv->v_type = VAR_STRING;
|
|
rettv->vval.v_string = vim_strnsave(p + nbyte, len);
|
|
}
|
|
|
|
/*
|
|
* "strpart()" function
|
|
*/
|
|
void
|
|
f_strpart(typval_T *argvars, typval_T *rettv)
|
|
{
|
|
char_u *p;
|
|
int n;
|
|
int len;
|
|
int slen;
|
|
int error = FALSE;
|
|
|
|
if (in_vim9script()
|
|
&& (check_for_string_arg(argvars, 0) == FAIL
|
|
|| check_for_number_arg(argvars, 1) == FAIL
|
|
|| check_for_opt_number_arg(argvars, 2) == FAIL
|
|
|| (argvars[2].v_type != VAR_UNKNOWN
|
|
&& check_for_opt_bool_arg(argvars, 3) == FAIL)))
|
|
return;
|
|
|
|
p = tv_get_string(&argvars[0]);
|
|
slen = (int)STRLEN(p);
|
|
|
|
n = (int)tv_get_number_chk(&argvars[1], &error);
|
|
if (error)
|
|
len = 0;
|
|
else if (argvars[2].v_type != VAR_UNKNOWN)
|
|
len = (int)tv_get_number(&argvars[2]);
|
|
else
|
|
len = slen - n; // default len: all bytes that are available.
|
|
|
|
// Only return the overlap between the specified part and the actual
|
|
// string.
|
|
if (n < 0)
|
|
{
|
|
len += n;
|
|
n = 0;
|
|
}
|
|
else if (n > slen)
|
|
n = slen;
|
|
if (len < 0)
|
|
len = 0;
|
|
else if (n + len > slen)
|
|
len = slen - n;
|
|
|
|
if (argvars[2].v_type != VAR_UNKNOWN && argvars[3].v_type != VAR_UNKNOWN)
|
|
{
|
|
int off;
|
|
|
|
// length in characters
|
|
for (off = n; off < slen && len > 0; --len)
|
|
off += mb_ptr2len(p + off);
|
|
len = off - n;
|
|
}
|
|
|
|
rettv->v_type = VAR_STRING;
|
|
rettv->vval.v_string = vim_strnsave(p + n, len);
|
|
}
|
|
|
|
/*
|
|
* "strridx()" function
|
|
*/
|
|
void
|
|
f_strridx(typval_T *argvars, typval_T *rettv)
|
|
{
|
|
char_u buf[NUMBUFLEN];
|
|
char_u *needle;
|
|
char_u *haystack;
|
|
char_u *rest;
|
|
char_u *lastmatch = NULL;
|
|
int haystack_len, end_idx;
|
|
|
|
if (in_vim9script()
|
|
&& (check_for_string_arg(argvars, 0) == FAIL
|
|
|| check_for_string_arg(argvars, 1) == FAIL
|
|
|| check_for_opt_number_arg(argvars, 2) == FAIL))
|
|
return;
|
|
|
|
needle = tv_get_string_chk(&argvars[1]);
|
|
haystack = tv_get_string_buf_chk(&argvars[0], buf);
|
|
|
|
rettv->vval.v_number = -1;
|
|
if (needle == NULL || haystack == NULL)
|
|
return; // type error; errmsg already given
|
|
|
|
haystack_len = (int)STRLEN(haystack);
|
|
if (argvars[2].v_type != VAR_UNKNOWN)
|
|
{
|
|
// Third argument: upper limit for index
|
|
end_idx = (int)tv_get_number_chk(&argvars[2], NULL);
|
|
if (end_idx < 0)
|
|
return; // can never find a match
|
|
}
|
|
else
|
|
end_idx = haystack_len;
|
|
|
|
if (*needle == NUL)
|
|
{
|
|
// Empty string matches past the end.
|
|
lastmatch = haystack + end_idx;
|
|
}
|
|
else
|
|
{
|
|
for (rest = haystack; *rest != '\0'; ++rest)
|
|
{
|
|
rest = (char_u *)strstr((char *)rest, (char *)needle);
|
|
if (rest == NULL || rest > haystack + end_idx)
|
|
break;
|
|
lastmatch = rest;
|
|
}
|
|
}
|
|
|
|
if (lastmatch == NULL)
|
|
rettv->vval.v_number = -1;
|
|
else
|
|
rettv->vval.v_number = (varnumber_T)(lastmatch - haystack);
|
|
}
|
|
|
|
/*
|
|
* "strtrans()" function
|
|
*/
|
|
void
|
|
f_strtrans(typval_T *argvars, typval_T *rettv)
|
|
{
|
|
if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
|
|
return;
|
|
|
|
rettv->v_type = VAR_STRING;
|
|
rettv->vval.v_string = transstr(tv_get_string(&argvars[0]));
|
|
}
|
|
|
|
|
|
/*
|
|
* "utf16idx()" function
|
|
*
|
|
* Converts a byte or character offset in a string to the corresponding UTF-16
|
|
* code unit offset.
|
|
*/
|
|
void
|
|
f_utf16idx(typval_T *argvars, typval_T *rettv)
|
|
{
|
|
rettv->vval.v_number = -1;
|
|
|
|
if (check_for_string_arg(argvars, 0) == FAIL
|
|
|| check_for_opt_number_arg(argvars, 1) == FAIL
|
|
|| check_for_opt_bool_arg(argvars, 2) == FAIL
|
|
|| (argvars[2].v_type != VAR_UNKNOWN
|
|
&& check_for_opt_bool_arg(argvars, 3) == FAIL))
|
|
return;
|
|
|
|
char_u *str = tv_get_string_chk(&argvars[0]);
|
|
varnumber_T idx = tv_get_number_chk(&argvars[1], NULL);
|
|
if (str == NULL || idx < 0)
|
|
return;
|
|
|
|
varnumber_T countcc = FALSE;
|
|
varnumber_T charidx = FALSE;
|
|
if (argvars[2].v_type != VAR_UNKNOWN)
|
|
{
|
|
countcc = tv_get_bool(&argvars[2]);
|
|
if (argvars[3].v_type != VAR_UNKNOWN)
|
|
charidx = tv_get_bool(&argvars[3]);
|
|
}
|
|
|
|
int (*ptr2len)(char_u *);
|
|
if (enc_utf8 && countcc)
|
|
ptr2len = utf_ptr2len;
|
|
else
|
|
ptr2len = mb_ptr2len;
|
|
|
|
char_u *p;
|
|
int len;
|
|
int utf16idx = 0;
|
|
for (p = str, len = 0; charidx ? idx >= 0 : p <= str + idx; len++)
|
|
{
|
|
if (*p == NUL)
|
|
{
|
|
// If the index is exactly the number of bytes or characters in the
|
|
// string then return the length of the string in utf-16 code
|
|
// units.
|
|
if (charidx ? (idx == 0) : (p == (str + idx)))
|
|
rettv->vval.v_number = len;
|
|
return;
|
|
}
|
|
utf16idx = len;
|
|
int clen = ptr2len(p);
|
|
int c = (clen > 1) ? utf_ptr2char(p) : *p;
|
|
if (c > 0xFFFF)
|
|
len++;
|
|
p += ptr2len(p);
|
|
if (charidx)
|
|
idx--;
|
|
}
|
|
|
|
rettv->vval.v_number = utf16idx;
|
|
}
|
|
|
|
/*
|
|
* "tolower(string)" function
|
|
*/
|
|
void
|
|
f_tolower(typval_T *argvars, typval_T *rettv)
|
|
{
|
|
if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
|
|
return;
|
|
|
|
rettv->v_type = VAR_STRING;
|
|
rettv->vval.v_string = strlow_save(tv_get_string(&argvars[0]));
|
|
}
|
|
|
|
/*
|
|
* "toupper(string)" function
|
|
*/
|
|
void
|
|
f_toupper(typval_T *argvars, typval_T *rettv)
|
|
{
|
|
if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
|
|
return;
|
|
|
|
rettv->v_type = VAR_STRING;
|
|
rettv->vval.v_string = strup_save(tv_get_string(&argvars[0]));
|
|
}
|
|
|
|
/*
|
|
* "tr(string, fromstr, tostr)" function
|
|
*/
|
|
void
|
|
f_tr(typval_T *argvars, typval_T *rettv)
|
|
{
|
|
char_u *in_str;
|
|
char_u *fromstr;
|
|
char_u *tostr;
|
|
char_u *p;
|
|
int inlen;
|
|
int fromlen;
|
|
int tolen;
|
|
int idx;
|
|
char_u *cpstr;
|
|
int cplen;
|
|
int first = TRUE;
|
|
char_u buf[NUMBUFLEN];
|
|
char_u buf2[NUMBUFLEN];
|
|
garray_T ga;
|
|
|
|
if (in_vim9script()
|
|
&& (check_for_string_arg(argvars, 0) == FAIL
|
|
|| check_for_string_arg(argvars, 1) == FAIL
|
|
|| check_for_string_arg(argvars, 2) == FAIL))
|
|
return;
|
|
|
|
in_str = tv_get_string(&argvars[0]);
|
|
fromstr = tv_get_string_buf_chk(&argvars[1], buf);
|
|
tostr = tv_get_string_buf_chk(&argvars[2], buf2);
|
|
|
|
// Default return value: empty string.
|
|
rettv->v_type = VAR_STRING;
|
|
rettv->vval.v_string = NULL;
|
|
if (fromstr == NULL || tostr == NULL)
|
|
return; // type error; errmsg already given
|
|
ga_init2(&ga, sizeof(char), 80);
|
|
|
|
if (!has_mbyte)
|
|
// not multi-byte: fromstr and tostr must be the same length
|
|
if (STRLEN(fromstr) != STRLEN(tostr))
|
|
{
|
|
error:
|
|
semsg(_(e_invalid_argument_str), fromstr);
|
|
ga_clear(&ga);
|
|
return;
|
|
}
|
|
|
|
// fromstr and tostr have to contain the same number of chars
|
|
while (*in_str != NUL)
|
|
{
|
|
if (has_mbyte)
|
|
{
|
|
inlen = (*mb_ptr2len)(in_str);
|
|
cpstr = in_str;
|
|
cplen = inlen;
|
|
idx = 0;
|
|
for (p = fromstr; *p != NUL; p += fromlen)
|
|
{
|
|
fromlen = (*mb_ptr2len)(p);
|
|
if (fromlen == inlen && STRNCMP(in_str, p, inlen) == 0)
|
|
{
|
|
for (p = tostr; *p != NUL; p += tolen)
|
|
{
|
|
tolen = (*mb_ptr2len)(p);
|
|
if (idx-- == 0)
|
|
{
|
|
cplen = tolen;
|
|
cpstr = p;
|
|
break;
|
|
}
|
|
}
|
|
if (*p == NUL) // tostr is shorter than fromstr
|
|
goto error;
|
|
break;
|
|
}
|
|
++idx;
|
|
}
|
|
|
|
if (first && cpstr == in_str)
|
|
{
|
|
// Check that fromstr and tostr have the same number of
|
|
// (multi-byte) characters. Done only once when a character
|
|
// of in_str doesn't appear in fromstr.
|
|
first = FALSE;
|
|
for (p = tostr; *p != NUL; p += tolen)
|
|
{
|
|
tolen = (*mb_ptr2len)(p);
|
|
--idx;
|
|
}
|
|
if (idx != 0)
|
|
goto error;
|
|
}
|
|
|
|
(void)ga_grow(&ga, cplen);
|
|
mch_memmove((char *)ga.ga_data + ga.ga_len, cpstr, (size_t)cplen);
|
|
ga.ga_len += cplen;
|
|
|
|
in_str += inlen;
|
|
}
|
|
else
|
|
{
|
|
// When not using multi-byte chars we can do it faster.
|
|
p = vim_strchr(fromstr, *in_str);
|
|
if (p != NULL)
|
|
ga_append(&ga, tostr[p - fromstr]);
|
|
else
|
|
ga_append(&ga, *in_str);
|
|
++in_str;
|
|
}
|
|
}
|
|
|
|
// add a terminating NUL
|
|
(void)ga_grow(&ga, 1);
|
|
ga_append(&ga, NUL);
|
|
|
|
rettv->vval.v_string = ga.ga_data;
|
|
}
|
|
|
|
/*
|
|
* "trim({expr})" function
|
|
*/
|
|
void
|
|
f_trim(typval_T *argvars, typval_T *rettv)
|
|
{
|
|
char_u buf1[NUMBUFLEN];
|
|
char_u buf2[NUMBUFLEN];
|
|
char_u *head;
|
|
char_u *mask = NULL;
|
|
char_u *tail;
|
|
char_u *prev;
|
|
char_u *p;
|
|
int c1;
|
|
int dir = 0;
|
|
|
|
rettv->v_type = VAR_STRING;
|
|
rettv->vval.v_string = NULL;
|
|
|
|
if (in_vim9script()
|
|
&& (check_for_string_arg(argvars, 0) == FAIL
|
|
|| check_for_opt_string_arg(argvars, 1) == FAIL
|
|
|| (argvars[1].v_type != VAR_UNKNOWN
|
|
&& check_for_opt_number_arg(argvars, 2) == FAIL)))
|
|
return;
|
|
|
|
head = tv_get_string_buf_chk(&argvars[0], buf1);
|
|
if (head == NULL)
|
|
return;
|
|
|
|
if (check_for_opt_string_arg(argvars, 1) == FAIL)
|
|
return;
|
|
|
|
if (argvars[1].v_type == VAR_STRING)
|
|
{
|
|
mask = tv_get_string_buf_chk(&argvars[1], buf2);
|
|
if (*mask == NUL)
|
|
mask = NULL;
|
|
|
|
if (argvars[2].v_type != VAR_UNKNOWN)
|
|
{
|
|
int error = 0;
|
|
|
|
// leading or trailing characters to trim
|
|
dir = (int)tv_get_number_chk(&argvars[2], &error);
|
|
if (error)
|
|
return;
|
|
if (dir < 0 || dir > 2)
|
|
{
|
|
semsg(_(e_invalid_argument_str), tv_get_string(&argvars[2]));
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (dir == 0 || dir == 1)
|
|
{
|
|
// Trim leading characters
|
|
while (*head != NUL)
|
|
{
|
|
c1 = PTR2CHAR(head);
|
|
if (mask == NULL)
|
|
{
|
|
if (c1 > ' ' && c1 != 0xa0)
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
for (p = mask; *p != NUL; MB_PTR_ADV(p))
|
|
if (c1 == PTR2CHAR(p))
|
|
break;
|
|
if (*p == NUL)
|
|
break;
|
|
}
|
|
MB_PTR_ADV(head);
|
|
}
|
|
}
|
|
|
|
tail = head + STRLEN(head);
|
|
if (dir == 0 || dir == 2)
|
|
{
|
|
// Trim trailing characters
|
|
for (; tail > head; tail = prev)
|
|
{
|
|
prev = tail;
|
|
MB_PTR_BACK(head, prev);
|
|
c1 = PTR2CHAR(prev);
|
|
if (mask == NULL)
|
|
{
|
|
if (c1 > ' ' && c1 != 0xa0)
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
for (p = mask; *p != NUL; MB_PTR_ADV(p))
|
|
if (c1 == PTR2CHAR(p))
|
|
break;
|
|
if (*p == NUL)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
rettv->vval.v_string = vim_strnsave(head, tail - head);
|
|
}
|
|
|
|
static char *e_printf = N_(e_insufficient_arguments_for_printf);
|
|
|
|
/*
|
|
* Get number argument from "idxp" entry in "tvs". First entry is 1.
|
|
*/
|
|
static varnumber_T
|
|
tv_nr(typval_T *tvs, int *idxp)
|
|
{
|
|
int idx = *idxp - 1;
|
|
varnumber_T n = 0;
|
|
int err = FALSE;
|
|
|
|
if (tvs[idx].v_type == VAR_UNKNOWN)
|
|
emsg(_(e_printf));
|
|
else
|
|
{
|
|
++*idxp;
|
|
n = tv_get_number_chk(&tvs[idx], &err);
|
|
if (err)
|
|
n = 0;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
/*
|
|
* Get string argument from "idxp" entry in "tvs". First entry is 1.
|
|
* If "tofree" is NULL tv_get_string_chk() is used. Some types (e.g. List)
|
|
* are not converted to a string.
|
|
* If "tofree" is not NULL echo_string() is used. All types are converted to
|
|
* a string with the same format as ":echo". The caller must free "*tofree".
|
|
* Returns NULL for an error.
|
|
*/
|
|
static char *
|
|
tv_str(typval_T *tvs, int *idxp, char_u **tofree)
|
|
{
|
|
int idx = *idxp - 1;
|
|
char *s = NULL;
|
|
static char_u numbuf[NUMBUFLEN];
|
|
|
|
if (tvs[idx].v_type == VAR_UNKNOWN)
|
|
emsg(_(e_printf));
|
|
else
|
|
{
|
|
++*idxp;
|
|
if (tofree != NULL)
|
|
s = (char *)echo_string(&tvs[idx], tofree, numbuf, get_copyID());
|
|
else
|
|
s = (char *)tv_get_string_chk(&tvs[idx]);
|
|
}
|
|
return s;
|
|
}
|
|
|
|
/*
|
|
* Get float argument from "idxp" entry in "tvs". First entry is 1.
|
|
*/
|
|
static double
|
|
tv_float(typval_T *tvs, int *idxp)
|
|
{
|
|
int idx = *idxp - 1;
|
|
double f = 0;
|
|
|
|
if (tvs[idx].v_type == VAR_UNKNOWN)
|
|
emsg(_(e_printf));
|
|
else
|
|
{
|
|
++*idxp;
|
|
if (tvs[idx].v_type == VAR_FLOAT)
|
|
f = tvs[idx].vval.v_float;
|
|
else if (tvs[idx].v_type == VAR_NUMBER)
|
|
f = (double)tvs[idx].vval.v_number;
|
|
else
|
|
emsg(_(e_expected_float_argument_for_printf));
|
|
}
|
|
return f;
|
|
}
|
|
|
|
#endif
|
|
|
|
/*
|
|
* Return the representation of infinity for printf() function:
|
|
* "-inf", "inf", "+inf", " inf", "-INF", "INF", "+INF" or " INF".
|
|
*/
|
|
static const char *
|
|
infinity_str(int positive,
|
|
char fmt_spec,
|
|
int force_sign,
|
|
int space_for_positive)
|
|
{
|
|
static const char *table[] =
|
|
{
|
|
"-inf", "inf", "+inf", " inf",
|
|
"-INF", "INF", "+INF", " INF"
|
|
};
|
|
int idx = positive * (1 + force_sign + force_sign * space_for_positive);
|
|
|
|
if (ASCII_ISUPPER(fmt_spec))
|
|
idx += 4;
|
|
return table[idx];
|
|
}
|
|
|
|
/*
|
|
* This code was included to provide a portable vsnprintf() and snprintf().
|
|
* Some systems may provide their own, but we always use this one for
|
|
* consistency.
|
|
*
|
|
* This code is based on snprintf.c - a portable implementation of snprintf
|
|
* by Mark Martinec <mark.martinec@ijs.si>, Version 2.2, 2000-10-06.
|
|
* Included with permission. It was heavily modified to fit in Vim.
|
|
* The original code, including useful comments, can be found here:
|
|
* http://www.ijs.si/software/snprintf/
|
|
*
|
|
* This snprintf() only supports the following conversion specifiers:
|
|
* s, c, d, u, o, x, X, p (and synonyms: i, D, U, O - see below)
|
|
* with flags: '-', '+', ' ', '0' and '#'.
|
|
* An asterisk is supported for field width as well as precision.
|
|
*
|
|
* Limited support for floating point was added: 'f', 'F', 'e', 'E', 'g', 'G'.
|
|
*
|
|
* Length modifiers 'h' (short int) and 'l' (long int) and 'll' (long long int)
|
|
* are supported. NOTE: for 'll' the argument is varnumber_T or uvarnumber_T.
|
|
*
|
|
* The locale is not used, the string is used as a byte string. This is only
|
|
* relevant for double-byte encodings where the second byte may be '%'.
|
|
*
|
|
* It is permitted for "str_m" to be zero, and it is permitted to specify NULL
|
|
* pointer for resulting string argument if "str_m" is zero (as per ISO C99).
|
|
*
|
|
* The return value is the number of characters which would be generated
|
|
* for the given input, excluding the trailing NUL. If this value
|
|
* is greater or equal to "str_m", not all characters from the result
|
|
* have been stored in str, output bytes beyond the ("str_m"-1) -th character
|
|
* are discarded. If "str_m" is greater than zero it is guaranteed
|
|
* the resulting string will be NUL-terminated.
|
|
*/
|
|
|
|
/*
|
|
* When va_list is not supported we only define vim_snprintf().
|
|
*
|
|
* vim_vsnprintf_typval() can be invoked with either "va_list" or a list of
|
|
* "typval_T". When the latter is not used it must be NULL.
|
|
*/
|
|
|
|
// When generating prototypes all of this is skipped, cproto doesn't
|
|
// understand this.
|
|
#ifndef PROTO
|
|
|
|
// Like vim_vsnprintf() but append to the string.
|
|
int
|
|
vim_snprintf_add(char *str, size_t str_m, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
int str_l;
|
|
size_t len = STRLEN(str);
|
|
size_t space;
|
|
|
|
if (str_m <= len)
|
|
space = 0;
|
|
else
|
|
space = str_m - len;
|
|
va_start(ap, fmt);
|
|
str_l = vim_vsnprintf(str + len, space, fmt, ap);
|
|
va_end(ap);
|
|
return str_l;
|
|
}
|
|
|
|
int
|
|
vim_snprintf(char *str, size_t str_m, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
int str_l;
|
|
|
|
va_start(ap, fmt);
|
|
str_l = vim_vsnprintf(str, str_m, fmt, ap);
|
|
va_end(ap);
|
|
return str_l;
|
|
}
|
|
|
|
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)
|
|
{
|
|
// allowed values: \0, h, l, L
|
|
char length_modifier = '\0';
|
|
|
|
// current conversion specifier character
|
|
char fmt_spec = '\0';
|
|
|
|
// parse 'h', 'l' and 'll' length modifiers
|
|
if (*type == 'h' || *type == 'l')
|
|
{
|
|
length_modifier = *type;
|
|
type++;
|
|
if (length_modifier == 'l' && *type == 'l')
|
|
{
|
|
// double l = __int64 / varnumber_T
|
|
length_modifier = 'L';
|
|
type++;
|
|
}
|
|
}
|
|
fmt_spec = *type;
|
|
|
|
// common synonyms:
|
|
switch (fmt_spec)
|
|
{
|
|
case 'i': fmt_spec = 'd'; break;
|
|
case '*': fmt_spec = 'd'; length_modifier = 'h'; break;
|
|
case 'D': fmt_spec = 'd'; length_modifier = 'l'; break;
|
|
case 'U': fmt_spec = 'u'; length_modifier = 'l'; break;
|
|
case 'O': fmt_spec = 'o'; length_modifier = 'l'; break;
|
|
default: break;
|
|
}
|
|
|
|
// 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_UNSIGNEDLONGLONGINT;
|
|
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)
|
|
{
|
|
switch (format_typeof(type))
|
|
{
|
|
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)
|
|
{
|
|
if (*ap_types == NULL || *num_posarg < arg)
|
|
{
|
|
int idx;
|
|
const char **new_types;
|
|
|
|
if (*ap_types == NULL)
|
|
new_types = ALLOC_CLEAR_MULT(const char *, arg);
|
|
else
|
|
new_types = vim_realloc((char **)*ap_types,
|
|
arg * sizeof(const char *));
|
|
|
|
if (new_types == NULL)
|
|
return FAIL;
|
|
|
|
for (idx = *num_posarg; idx < arg; ++idx)
|
|
new_types[idx] = NULL;
|
|
|
|
*ap_types = new_types;
|
|
*num_posarg = arg;
|
|
}
|
|
|
|
if ((*ap_types)[arg - 1] != NULL)
|
|
{
|
|
if ((*ap_types)[arg - 1][0] == '*' || type[0] == '*')
|
|
{
|
|
const char *pt = type;
|
|
if (pt[0] == '*')
|
|
pt = (*ap_types)[arg - 1];
|
|
|
|
if (pt[0] != '*')
|
|
{
|
|
switch (pt[0])
|
|
{
|
|
case 'd': case 'i': break;
|
|
default:
|
|
semsg(_(e_positional_num_field_spec_reused_str_str), arg, format_typename((*ap_types)[arg - 1]), format_typename(type));
|
|
return FAIL;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (format_typeof(type) != format_typeof((*ap_types)[arg - 1]))
|
|
{
|
|
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 void
|
|
format_overflow_error(const char *pstart)
|
|
{
|
|
size_t arglen = 0;
|
|
char *argcopy = NULL;
|
|
const char *p = pstart;
|
|
|
|
while (VIM_ISDIGIT((int)(*p)))
|
|
++p;
|
|
|
|
arglen = p - pstart;
|
|
argcopy = ALLOC_CLEAR_MULT(char, arglen + 1);
|
|
if (argcopy != NULL)
|
|
{
|
|
strncpy(argcopy, pstart, arglen);
|
|
semsg(_( e_val_too_large), argcopy);
|
|
free(argcopy);
|
|
}
|
|
else
|
|
semsg(_(e_out_of_memory_allocating_nr_bytes), arglen);
|
|
}
|
|
|
|
#define MAX_ALLOWED_STRING_WIDTH 6400
|
|
|
|
static int
|
|
get_unsigned_int(
|
|
const char *pstart,
|
|
const char **p,
|
|
unsigned int *uj,
|
|
int overflow_err)
|
|
{
|
|
*uj = **p - '0';
|
|
++*p;
|
|
|
|
while (VIM_ISDIGIT((int)(**p)) && *uj < MAX_ALLOWED_STRING_WIDTH)
|
|
{
|
|
*uj = 10 * *uj + (unsigned int)(**p - '0');
|
|
++*p;
|
|
}
|
|
|
|
if (*uj > MAX_ALLOWED_STRING_WIDTH)
|
|
{
|
|
if (overflow_err)
|
|
{
|
|
format_overflow_error(pstart);
|
|
return FAIL;
|
|
}
|
|
else
|
|
*uj = MAX_ALLOWED_STRING_WIDTH;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
|
|
static int
|
|
parse_fmt_types(
|
|
const char ***ap_types,
|
|
int *num_posarg,
|
|
const char *fmt,
|
|
typval_T *tvs UNUSED
|
|
)
|
|
{
|
|
const char *p = fmt;
|
|
const char *arg = NULL;
|
|
|
|
int any_pos = 0;
|
|
int any_arg = 0;
|
|
int arg_idx;
|
|
|
|
#define CHECK_POS_ARG do { \
|
|
if (any_pos && any_arg) \
|
|
{ \
|
|
semsg(_( e_cannot_mix_positional_and_non_positional_str), fmt); \
|
|
goto error; \
|
|
} \
|
|
} while (0);
|
|
|
|
if (p == NULL)
|
|
return OK;
|
|
|
|
while (*p != NUL)
|
|
{
|
|
if (*p != '%')
|
|
{
|
|
char *q = strchr(p + 1, '%');
|
|
size_t n = (q == NULL) ? STRLEN(p) : (size_t)(q - p);
|
|
|
|
p += n;
|
|
}
|
|
else
|
|
{
|
|
// allowed values: \0, h, l, L
|
|
char length_modifier = '\0';
|
|
|
|
// variable for positional arg
|
|
int pos_arg = -1;
|
|
const char *ptype = NULL;
|
|
const char *pstart = p+1;
|
|
|
|
p++; // skip '%'
|
|
|
|
// First check to see if we find a positional
|
|
// argument specifier
|
|
ptype = p;
|
|
|
|
while (VIM_ISDIGIT(*ptype))
|
|
++ptype;
|
|
|
|
if (*ptype == '$')
|
|
{
|
|
if (*p == '0')
|
|
{
|
|
// 0 flag at the wrong place
|
|
semsg(_( e_invalid_format_specifier_str), fmt);
|
|
goto error;
|
|
}
|
|
|
|
// Positional argument
|
|
unsigned int uj;
|
|
|
|
if (get_unsigned_int(pstart, &p, &uj, tvs != NULL) == FAIL)
|
|
goto error;
|
|
|
|
pos_arg = uj;
|
|
|
|
any_pos = 1;
|
|
CHECK_POS_ARG;
|
|
|
|
++p;
|
|
}
|
|
|
|
// parse flags
|
|
while (*p == '0' || *p == '-' || *p == '+' || *p == ' '
|
|
|| *p == '#' || *p == '\'')
|
|
{
|
|
switch (*p)
|
|
{
|
|
case '0': break;
|
|
case '-': break;
|
|
case '+': break;
|
|
case ' ': // If both the ' ' and '+' flags appear, the ' '
|
|
// flag should be ignored
|
|
break;
|
|
case '#': break;
|
|
case '\'': break;
|
|
}
|
|
p++;
|
|
}
|
|
// If the '0' and '-' flags both appear, the '0' flag should be
|
|
// ignored.
|
|
|
|
// parse field width
|
|
if (*(arg = p) == '*')
|
|
{
|
|
p++;
|
|
|
|
if (VIM_ISDIGIT((int)(*p)))
|
|
{
|
|
// Positional argument field width
|
|
unsigned int uj;
|
|
|
|
if (get_unsigned_int(arg + 1, &p, &uj, tvs != NULL) == FAIL)
|
|
goto error;
|
|
|
|
if (*p != '$')
|
|
{
|
|
semsg(_( e_invalid_format_specifier_str), fmt);
|
|
goto error;
|
|
}
|
|
else
|
|
{
|
|
++p;
|
|
any_pos = 1;
|
|
CHECK_POS_ARG;
|
|
|
|
if (adjust_types(ap_types, uj, num_posarg, arg) == FAIL)
|
|
goto error;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
any_arg = 1;
|
|
CHECK_POS_ARG;
|
|
}
|
|
}
|
|
else if (VIM_ISDIGIT((int)(*p)))
|
|
{
|
|
// size_t could be wider than unsigned int; make sure we treat
|
|
// argument like common implementations do
|
|
const char *digstart = p;
|
|
unsigned int uj;
|
|
|
|
if (get_unsigned_int(digstart, &p, &uj, tvs != NULL) == FAIL)
|
|
goto error;
|
|
|
|
if (*p == '$')
|
|
{
|
|
semsg(_( e_invalid_format_specifier_str), fmt);
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
// parse precision
|
|
if (*p == '.')
|
|
{
|
|
p++;
|
|
|
|
if (*(arg = p) == '*')
|
|
{
|
|
p++;
|
|
|
|
if (VIM_ISDIGIT((int)(*p)))
|
|
{
|
|
// Parse precision
|
|
unsigned int uj;
|
|
|
|
if (get_unsigned_int(arg + 1, &p, &uj, tvs != NULL) == FAIL)
|
|
goto error;
|
|
|
|
if (*p == '$')
|
|
{
|
|
any_pos = 1;
|
|
CHECK_POS_ARG;
|
|
|
|
++p;
|
|
|
|
if (adjust_types(ap_types, uj, num_posarg, arg) == FAIL)
|
|
goto error;
|
|
}
|
|
else
|
|
{
|
|
semsg(_( e_invalid_format_specifier_str), fmt);
|
|
goto error;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
any_arg = 1;
|
|
CHECK_POS_ARG;
|
|
}
|
|
}
|
|
else if (VIM_ISDIGIT((int)(*p)))
|
|
{
|
|
// size_t could be wider than unsigned int; make sure we
|
|
// treat argument like common implementations do
|
|
const char *digstart = p;
|
|
unsigned int uj;
|
|
|
|
if (get_unsigned_int(digstart, &p, &uj, tvs != NULL) == FAIL)
|
|
goto error;
|
|
|
|
if (*p == '$')
|
|
{
|
|
semsg(_( e_invalid_format_specifier_str), fmt);
|
|
goto error;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pos_arg != -1)
|
|
{
|
|
any_pos = 1;
|
|
CHECK_POS_ARG;
|
|
|
|
ptype = p;
|
|
}
|
|
|
|
// parse 'h', 'l' and 'll' length modifiers
|
|
if (*p == 'h' || *p == 'l')
|
|
{
|
|
length_modifier = *p;
|
|
p++;
|
|
if (length_modifier == 'l' && *p == 'l')
|
|
{
|
|
// double l = __int64 / varnumber_T
|
|
// length_modifier = 'L';
|
|
p++;
|
|
}
|
|
}
|
|
|
|
switch (*p)
|
|
{
|
|
// Check for known format specifiers. % is special!
|
|
case 'i':
|
|
case '*':
|
|
case 'd':
|
|
case 'u':
|
|
case 'o':
|
|
case 'D':
|
|
case 'U':
|
|
case 'O':
|
|
case 'x':
|
|
case 'X':
|
|
case 'b':
|
|
case 'B':
|
|
case 'c':
|
|
case 's':
|
|
case 'S':
|
|
case 'p':
|
|
case 'f':
|
|
case 'F':
|
|
case 'e':
|
|
case 'E':
|
|
case 'g':
|
|
case 'G':
|
|
if (pos_arg != -1)
|
|
{
|
|
if (adjust_types(ap_types, pos_arg, num_posarg, ptype) == FAIL)
|
|
goto error;
|
|
}
|
|
else
|
|
{
|
|
any_arg = 1;
|
|
CHECK_POS_ARG;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
if (pos_arg != -1)
|
|
{
|
|
semsg(_( e_cannot_mix_positional_and_non_positional_str), fmt);
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
if (*p != NUL)
|
|
p++; // step over the just processed conversion specifier
|
|
}
|
|
}
|
|
|
|
for (arg_idx = 0; arg_idx < *num_posarg; ++arg_idx)
|
|
{
|
|
if ((*ap_types)[arg_idx] == NULL)
|
|
{
|
|
semsg(_(e_fmt_arg_nr_unused_str), arg_idx + 1, fmt);
|
|
goto error;
|
|
}
|
|
|
|
# if defined(FEAT_EVAL)
|
|
if (tvs != NULL && tvs[arg_idx].v_type == VAR_UNKNOWN)
|
|
{
|
|
semsg(_(e_positional_nr_out_of_bounds_str), arg_idx + 1, fmt);
|
|
goto error;
|
|
}
|
|
# endif
|
|
}
|
|
|
|
return OK;
|
|
|
|
error:
|
|
vim_free((char**)*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,
|
|
const char *fmt)
|
|
{
|
|
int arg_min = 0;
|
|
|
|
if (*arg_cur + 1 == *arg_idx)
|
|
{
|
|
++*arg_cur;
|
|
++*arg_idx;
|
|
return;
|
|
}
|
|
|
|
if (*arg_cur >= *arg_idx)
|
|
{
|
|
// Reset ap to ap_start and skip arg_idx - 1 types
|
|
va_end(*ap);
|
|
va_copy(*ap, ap_start);
|
|
}
|
|
else
|
|
{
|
|
// Skip over any we should skip
|
|
arg_min = *arg_cur;
|
|
}
|
|
|
|
for (*arg_cur = arg_min; *arg_cur < *arg_idx - 1; ++*arg_cur)
|
|
{
|
|
const char *p;
|
|
|
|
if (ap_types == NULL || ap_types[*arg_cur] == NULL)
|
|
{
|
|
siemsg(e_aptypes_is_null_nr_str, *arg_cur, fmt);
|
|
return;
|
|
}
|
|
|
|
p = ap_types[*arg_cur];
|
|
|
|
int fmt_type = format_typeof(p);
|
|
|
|
// get parameter value, do initial processing
|
|
switch (fmt_type)
|
|
{
|
|
case TYPE_PERCENT:
|
|
case TYPE_UNKNOWN:
|
|
break;
|
|
|
|
case TYPE_CHAR:
|
|
va_arg(*ap, int);
|
|
break;
|
|
|
|
case TYPE_STRING:
|
|
va_arg(*ap, char *);
|
|
break;
|
|
|
|
case TYPE_POINTER:
|
|
va_arg(*ap, void *);
|
|
break;
|
|
|
|
case TYPE_INT:
|
|
va_arg(*ap, int);
|
|
break;
|
|
|
|
case TYPE_LONGINT:
|
|
va_arg(*ap, long int);
|
|
break;
|
|
|
|
case TYPE_LONGLONGINT:
|
|
va_arg(*ap, varnumber_T);
|
|
break;
|
|
|
|
case TYPE_UNSIGNEDINT:
|
|
va_arg(*ap, unsigned int);
|
|
break;
|
|
|
|
case TYPE_UNSIGNEDLONGINT:
|
|
va_arg(*ap, unsigned long int);
|
|
break;
|
|
|
|
case TYPE_UNSIGNEDLONGLONGINT:
|
|
va_arg(*ap, uvarnumber_T);
|
|
break;
|
|
|
|
case TYPE_FLOAT:
|
|
va_arg(*ap, double);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Because we know that after we return from this call,
|
|
// a va_arg() call is made, we can pre-emptively
|
|
// increment the current argument index.
|
|
++*arg_cur;
|
|
++*arg_idx;
|
|
|
|
return;
|
|
}
|
|
|
|
int
|
|
vim_vsnprintf_typval(
|
|
char *str,
|
|
size_t str_m,
|
|
const char *fmt,
|
|
va_list ap_start,
|
|
typval_T *tvs)
|
|
{
|
|
size_t str_l = 0;
|
|
const char *p = fmt;
|
|
int arg_cur = 0;
|
|
int num_posarg = 0;
|
|
int arg_idx = 1;
|
|
va_list ap;
|
|
const char **ap_types = NULL;
|
|
|
|
if (parse_fmt_types(&ap_types, &num_posarg, fmt, tvs) == FAIL)
|
|
return 0;
|
|
|
|
va_copy(ap, ap_start);
|
|
|
|
if (p == NULL)
|
|
p = "";
|
|
while (*p != NUL)
|
|
{
|
|
if (*p != '%')
|
|
{
|
|
char *q = strchr(p + 1, '%');
|
|
size_t n = (q == NULL) ? STRLEN(p) : (size_t)(q - p);
|
|
|
|
// Copy up to the next '%' or NUL without any changes.
|
|
if (str_l < str_m)
|
|
{
|
|
size_t avail = str_m - str_l;
|
|
|
|
mch_memmove(str + str_l, p, n > avail ? avail : n);
|
|
}
|
|
p += n;
|
|
str_l += n;
|
|
}
|
|
else
|
|
{
|
|
size_t min_field_width = 0, precision = 0;
|
|
int zero_padding = 0, precision_specified = 0, justify_left = 0;
|
|
int alternate_form = 0, force_sign = 0;
|
|
|
|
// If both the ' ' and '+' flags appear, the ' ' flag should be
|
|
// ignored.
|
|
int space_for_positive = 1;
|
|
|
|
// allowed values: \0, h, l, L
|
|
char length_modifier = '\0';
|
|
|
|
// temporary buffer for simple numeric->string conversion
|
|
# define TMP_LEN 350 // On my system 1e308 is the biggest number possible.
|
|
// That sounds reasonable to use as the maximum
|
|
// printable.
|
|
char tmp[TMP_LEN];
|
|
|
|
// string address in case of string argument
|
|
const char *str_arg = NULL;
|
|
|
|
// natural field width of arg without padding and sign
|
|
size_t str_arg_l;
|
|
|
|
// unsigned char argument value - only defined for c conversion.
|
|
// N.B. standard explicitly states the char argument for the c
|
|
// conversion is unsigned
|
|
unsigned char uchar_arg;
|
|
|
|
// number of zeros to be inserted for numeric conversions as
|
|
// required by the precision or minimal field width
|
|
size_t number_of_zeros_to_pad = 0;
|
|
|
|
// index into tmp where zero padding is to be inserted
|
|
size_t zero_padding_insertion_ind = 0;
|
|
|
|
// current conversion specifier character
|
|
char fmt_spec = '\0';
|
|
|
|
// buffer for 's' and 'S' specs
|
|
char_u *tofree = NULL;
|
|
|
|
// variables for positional arg
|
|
int pos_arg = -1;
|
|
const char *ptype;
|
|
|
|
|
|
p++; // skip '%'
|
|
|
|
// First check to see if we find a positional
|
|
// argument specifier
|
|
ptype = p;
|
|
|
|
while (VIM_ISDIGIT(*ptype))
|
|
++ptype;
|
|
|
|
if (*ptype == '$')
|
|
{
|
|
// Positional argument
|
|
const char *digstart = p;
|
|
unsigned int uj;
|
|
|
|
if (get_unsigned_int(digstart, &p, &uj, tvs != NULL) == FAIL)
|
|
goto error;
|
|
|
|
pos_arg = uj;
|
|
|
|
++p;
|
|
}
|
|
|
|
// parse flags
|
|
while (*p == '0' || *p == '-' || *p == '+' || *p == ' '
|
|
|| *p == '#' || *p == '\'')
|
|
{
|
|
switch (*p)
|
|
{
|
|
case '0': zero_padding = 1; break;
|
|
case '-': justify_left = 1; break;
|
|
case '+': force_sign = 1; space_for_positive = 0; break;
|
|
case ' ': force_sign = 1;
|
|
// If both the ' ' and '+' flags appear, the ' '
|
|
// flag should be ignored
|
|
break;
|
|
case '#': alternate_form = 1; break;
|
|
case '\'': break;
|
|
}
|
|
p++;
|
|
}
|
|
// If the '0' and '-' flags both appear, the '0' flag should be
|
|
// ignored.
|
|
|
|
// parse field width
|
|
if (*p == '*')
|
|
{
|
|
int j;
|
|
const char *digstart = p + 1;
|
|
|
|
p++;
|
|
|
|
if (VIM_ISDIGIT((int)(*p)))
|
|
{
|
|
// Positional argument field width
|
|
unsigned int uj;
|
|
|
|
if (get_unsigned_int(digstart, &p, &uj, tvs != NULL) == FAIL)
|
|
goto error;
|
|
|
|
arg_idx = uj;
|
|
|
|
++p;
|
|
}
|
|
|
|
j =
|
|
# if defined(FEAT_EVAL)
|
|
tvs != NULL ? tv_nr(tvs, &arg_idx) :
|
|
# endif
|
|
(skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
|
|
&arg_cur, fmt),
|
|
va_arg(ap, int));
|
|
|
|
if (j > MAX_ALLOWED_STRING_WIDTH)
|
|
{
|
|
if (tvs != NULL)
|
|
{
|
|
format_overflow_error(digstart);
|
|
goto error;
|
|
}
|
|
else
|
|
j = MAX_ALLOWED_STRING_WIDTH;
|
|
}
|
|
|
|
if (j >= 0)
|
|
min_field_width = j;
|
|
else
|
|
{
|
|
min_field_width = -j;
|
|
justify_left = 1;
|
|
}
|
|
}
|
|
else if (VIM_ISDIGIT((int)(*p)))
|
|
{
|
|
// size_t could be wider than unsigned int; make sure we treat
|
|
// argument like common implementations do
|
|
const char *digstart = p;
|
|
unsigned int uj;
|
|
|
|
if (get_unsigned_int(digstart, &p, &uj, tvs != NULL) == FAIL)
|
|
goto error;
|
|
|
|
min_field_width = uj;
|
|
}
|
|
|
|
// parse precision
|
|
if (*p == '.')
|
|
{
|
|
p++;
|
|
precision_specified = 1;
|
|
|
|
if (VIM_ISDIGIT((int)(*p)))
|
|
{
|
|
// size_t could be wider than unsigned int; make sure we
|
|
// treat argument like common implementations do
|
|
const char *digstart = p;
|
|
unsigned int uj;
|
|
|
|
if (get_unsigned_int(digstart, &p, &uj, tvs != NULL) == FAIL)
|
|
goto error;
|
|
|
|
precision = uj;
|
|
}
|
|
else if (*p == '*')
|
|
{
|
|
int j;
|
|
const char *digstart = p;
|
|
|
|
p++;
|
|
|
|
if (VIM_ISDIGIT((int)(*p)))
|
|
{
|
|
// positional argument
|
|
unsigned int uj;
|
|
|
|
if (get_unsigned_int(digstart, &p, &uj, tvs != NULL) == FAIL)
|
|
goto error;
|
|
|
|
arg_idx = uj;
|
|
|
|
++p;
|
|
}
|
|
|
|
j =
|
|
# if defined(FEAT_EVAL)
|
|
tvs != NULL ? tv_nr(tvs, &arg_idx) :
|
|
# endif
|
|
(skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
|
|
&arg_cur, fmt),
|
|
va_arg(ap, int));
|
|
|
|
if (j > MAX_ALLOWED_STRING_WIDTH)
|
|
{
|
|
if (tvs != NULL)
|
|
{
|
|
format_overflow_error(digstart);
|
|
goto error;
|
|
}
|
|
else
|
|
j = MAX_ALLOWED_STRING_WIDTH;
|
|
}
|
|
|
|
if (j >= 0)
|
|
precision = j;
|
|
else
|
|
{
|
|
precision_specified = 0;
|
|
precision = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// parse 'h', 'l' and 'll' length modifiers
|
|
if (*p == 'h' || *p == 'l')
|
|
{
|
|
length_modifier = *p;
|
|
p++;
|
|
if (length_modifier == 'l' && *p == 'l')
|
|
{
|
|
// double l = __int64 / varnumber_T
|
|
length_modifier = 'L';
|
|
p++;
|
|
}
|
|
}
|
|
fmt_spec = *p;
|
|
|
|
// common synonyms:
|
|
switch (fmt_spec)
|
|
{
|
|
case 'i': fmt_spec = 'd'; break;
|
|
case 'D': fmt_spec = 'd'; length_modifier = 'l'; break;
|
|
case 'U': fmt_spec = 'u'; length_modifier = 'l'; break;
|
|
case 'O': fmt_spec = 'o'; length_modifier = 'l'; break;
|
|
default: break;
|
|
}
|
|
|
|
# if defined(FEAT_EVAL)
|
|
switch (fmt_spec)
|
|
{
|
|
case 'd': case 'u': case 'o': case 'x': case 'X':
|
|
if (tvs != NULL && length_modifier == '\0')
|
|
length_modifier = 'L';
|
|
}
|
|
# endif
|
|
|
|
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
|
|
case '%':
|
|
case 'c':
|
|
case 's':
|
|
case 'S':
|
|
str_arg_l = 1;
|
|
switch (fmt_spec)
|
|
{
|
|
case '%':
|
|
str_arg = p;
|
|
break;
|
|
|
|
case 'c':
|
|
{
|
|
int j;
|
|
|
|
j =
|
|
# if defined(FEAT_EVAL)
|
|
tvs != NULL ? tv_nr(tvs, &arg_idx) :
|
|
# endif
|
|
(skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
|
|
&arg_cur, fmt),
|
|
va_arg(ap, int));
|
|
|
|
// standard demands unsigned char
|
|
uchar_arg = (unsigned char)j;
|
|
str_arg = (char *)&uchar_arg;
|
|
break;
|
|
}
|
|
|
|
case 's':
|
|
case 'S':
|
|
str_arg =
|
|
# if defined(FEAT_EVAL)
|
|
tvs != NULL ? tv_str(tvs, &arg_idx, &tofree) :
|
|
# endif
|
|
(skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
|
|
&arg_cur, fmt),
|
|
va_arg(ap, char *));
|
|
|
|
if (str_arg == NULL)
|
|
{
|
|
str_arg = "[NULL]";
|
|
str_arg_l = 6;
|
|
}
|
|
// make sure not to address string beyond the specified
|
|
// precision !!!
|
|
else if (!precision_specified)
|
|
str_arg_l = strlen(str_arg);
|
|
// truncate string if necessary as requested by precision
|
|
else if (precision == 0)
|
|
str_arg_l = 0;
|
|
else
|
|
{
|
|
// memchr on HP does not like n > 2^31 !!!
|
|
char *q = memchr(str_arg, '\0',
|
|
precision <= (size_t)0x7fffffffL ? precision
|
|
: (size_t)0x7fffffffL);
|
|
|
|
str_arg_l = (q == NULL) ? precision
|
|
: (size_t)(q - str_arg);
|
|
}
|
|
if (fmt_spec == 'S')
|
|
{
|
|
char_u *p1;
|
|
size_t i;
|
|
int cell;
|
|
|
|
for (i = 0, p1 = (char_u *)str_arg; *p1;
|
|
p1 += mb_ptr2len(p1))
|
|
{
|
|
cell = mb_ptr2cells(p1);
|
|
if (precision_specified && i + cell > precision)
|
|
break;
|
|
i += cell;
|
|
}
|
|
|
|
str_arg_l = p1 - (char_u *)str_arg;
|
|
if (min_field_width != 0)
|
|
min_field_width += str_arg_l - i;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
|
|
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)
|
|
int arg_sign = 0;
|
|
|
|
// only set for length modifier h, or for no length
|
|
// modifiers
|
|
int int_arg = 0;
|
|
unsigned int uint_arg = 0;
|
|
|
|
// only set for length modifier l
|
|
long int long_arg = 0;
|
|
unsigned long int ulong_arg = 0;
|
|
|
|
// only set for length modifier ll
|
|
varnumber_T llong_arg = 0;
|
|
uvarnumber_T ullong_arg = 0;
|
|
|
|
// only set for b conversion
|
|
uvarnumber_T bin_arg = 0;
|
|
|
|
// pointer argument value -only defined for p
|
|
// conversion
|
|
void *ptr_arg = NULL;
|
|
|
|
if (fmt_spec == 'p')
|
|
{
|
|
length_modifier = '\0';
|
|
ptr_arg =
|
|
# if defined(FEAT_EVAL)
|
|
tvs != NULL ? (void *)tv_str(tvs, &arg_idx,
|
|
NULL) :
|
|
# endif
|
|
(skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
|
|
&arg_cur, fmt),
|
|
va_arg(ap, void *));
|
|
|
|
if (ptr_arg != NULL)
|
|
arg_sign = 1;
|
|
}
|
|
else if (fmt_spec == 'b' || fmt_spec == 'B')
|
|
{
|
|
bin_arg =
|
|
# if defined(FEAT_EVAL)
|
|
tvs != NULL ?
|
|
(uvarnumber_T)tv_nr(tvs, &arg_idx) :
|
|
# endif
|
|
(skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
|
|
&arg_cur, fmt),
|
|
va_arg(ap, uvarnumber_T));
|
|
|
|
if (bin_arg != 0)
|
|
arg_sign = 1;
|
|
}
|
|
else if (fmt_spec == 'd')
|
|
{
|
|
// signed
|
|
switch (length_modifier)
|
|
{
|
|
case '\0':
|
|
case 'h':
|
|
// char and short arguments are passed as int.
|
|
int_arg =
|
|
# if defined(FEAT_EVAL)
|
|
tvs != NULL ? tv_nr(tvs, &arg_idx) :
|
|
# endif
|
|
(skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
|
|
&arg_cur, fmt),
|
|
va_arg(ap, int));
|
|
|
|
if (int_arg > 0)
|
|
arg_sign = 1;
|
|
else if (int_arg < 0)
|
|
arg_sign = -1;
|
|
break;
|
|
case 'l':
|
|
long_arg =
|
|
# if defined(FEAT_EVAL)
|
|
tvs != NULL ? tv_nr(tvs, &arg_idx) :
|
|
# endif
|
|
(skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
|
|
&arg_cur, fmt),
|
|
va_arg(ap, long int));
|
|
|
|
if (long_arg > 0)
|
|
arg_sign = 1;
|
|
else if (long_arg < 0)
|
|
arg_sign = -1;
|
|
break;
|
|
case 'L':
|
|
llong_arg =
|
|
# if defined(FEAT_EVAL)
|
|
tvs != NULL ? tv_nr(tvs, &arg_idx) :
|
|
# endif
|
|
(skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
|
|
&arg_cur, fmt),
|
|
va_arg(ap, varnumber_T));
|
|
|
|
if (llong_arg > 0)
|
|
arg_sign = 1;
|
|
else if (llong_arg < 0)
|
|
arg_sign = -1;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// unsigned
|
|
switch (length_modifier)
|
|
{
|
|
case '\0':
|
|
case 'h':
|
|
uint_arg =
|
|
# if defined(FEAT_EVAL)
|
|
tvs != NULL ? (unsigned)
|
|
tv_nr(tvs, &arg_idx) :
|
|
# endif
|
|
(skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
|
|
&arg_cur, fmt),
|
|
va_arg(ap, unsigned int));
|
|
|
|
if (uint_arg != 0)
|
|
arg_sign = 1;
|
|
break;
|
|
case 'l':
|
|
ulong_arg =
|
|
# if defined(FEAT_EVAL)
|
|
tvs != NULL ? (unsigned long)
|
|
tv_nr(tvs, &arg_idx) :
|
|
# endif
|
|
(skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
|
|
&arg_cur, fmt),
|
|
va_arg(ap, unsigned long int));
|
|
|
|
if (ulong_arg != 0)
|
|
arg_sign = 1;
|
|
break;
|
|
case 'L':
|
|
ullong_arg =
|
|
# if defined(FEAT_EVAL)
|
|
tvs != NULL ? (uvarnumber_T)
|
|
tv_nr(tvs, &arg_idx) :
|
|
# endif
|
|
(skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
|
|
&arg_cur, fmt),
|
|
va_arg(ap, uvarnumber_T));
|
|
|
|
if (ullong_arg != 0)
|
|
arg_sign = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
str_arg = tmp;
|
|
str_arg_l = 0;
|
|
|
|
// NOTE:
|
|
// For d, i, u, o, x, and X conversions, if precision is
|
|
// specified, the '0' flag should be ignored. This is so
|
|
// with Solaris 2.6, Digital UNIX 4.0, HPUX 10, Linux,
|
|
// FreeBSD, NetBSD; but not with Perl.
|
|
if (precision_specified)
|
|
zero_padding = 0;
|
|
if (fmt_spec == 'd')
|
|
{
|
|
if (force_sign && arg_sign >= 0)
|
|
tmp[str_arg_l++] = space_for_positive ? ' ' : '+';
|
|
// leave negative numbers for sprintf to handle, to
|
|
// avoid handling tricky cases like (short int)-32768
|
|
}
|
|
else if (alternate_form)
|
|
{
|
|
if (arg_sign != 0
|
|
&& (fmt_spec == 'b' || fmt_spec == 'B'
|
|
|| fmt_spec == 'x' || fmt_spec == 'X') )
|
|
{
|
|
tmp[str_arg_l++] = '0';
|
|
tmp[str_arg_l++] = fmt_spec;
|
|
}
|
|
// alternate form should have no effect for p
|
|
// conversion, but ...
|
|
}
|
|
|
|
zero_padding_insertion_ind = str_arg_l;
|
|
if (!precision_specified)
|
|
precision = 1; // default precision is 1
|
|
if (precision == 0 && arg_sign == 0)
|
|
{
|
|
// When zero value is formatted with an explicit
|
|
// precision 0, the resulting formatted string is
|
|
// empty (d, i, u, b, B, o, x, X, p).
|
|
}
|
|
else
|
|
{
|
|
char f[6];
|
|
int f_l = 0;
|
|
|
|
// construct a simple format string for sprintf
|
|
f[f_l++] = '%';
|
|
if (!length_modifier)
|
|
;
|
|
else if (length_modifier == 'L')
|
|
{
|
|
# ifdef MSWIN
|
|
f[f_l++] = 'I';
|
|
f[f_l++] = '6';
|
|
f[f_l++] = '4';
|
|
# else
|
|
f[f_l++] = 'l';
|
|
f[f_l++] = 'l';
|
|
# endif
|
|
}
|
|
else
|
|
f[f_l++] = length_modifier;
|
|
f[f_l++] = fmt_spec;
|
|
f[f_l++] = '\0';
|
|
|
|
if (fmt_spec == 'p')
|
|
str_arg_l += sprintf(tmp + str_arg_l, f, ptr_arg);
|
|
else if (fmt_spec == 'b' || fmt_spec == 'B')
|
|
{
|
|
char b[8 * sizeof(uvarnumber_T)];
|
|
size_t b_l = 0;
|
|
uvarnumber_T bn = bin_arg;
|
|
|
|
do
|
|
{
|
|
b[sizeof(b) - ++b_l] = '0' + (bn & 0x1);
|
|
bn >>= 1;
|
|
}
|
|
while (bn != 0);
|
|
|
|
memcpy(tmp + str_arg_l, b + sizeof(b) - b_l, b_l);
|
|
str_arg_l += b_l;
|
|
}
|
|
else if (fmt_spec == 'd')
|
|
{
|
|
// signed
|
|
switch (length_modifier)
|
|
{
|
|
case '\0': str_arg_l += sprintf(
|
|
tmp + str_arg_l, f,
|
|
int_arg);
|
|
break;
|
|
case 'h': str_arg_l += sprintf(
|
|
tmp + str_arg_l, f,
|
|
(short)int_arg);
|
|
break;
|
|
case 'l': str_arg_l += sprintf(
|
|
tmp + str_arg_l, f, long_arg);
|
|
break;
|
|
case 'L': str_arg_l += sprintf(
|
|
tmp + str_arg_l, f, llong_arg);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// unsigned
|
|
switch (length_modifier)
|
|
{
|
|
case '\0': str_arg_l += sprintf(
|
|
tmp + str_arg_l, f,
|
|
uint_arg);
|
|
break;
|
|
case 'h': str_arg_l += sprintf(
|
|
tmp + str_arg_l, f,
|
|
(unsigned short)uint_arg);
|
|
break;
|
|
case 'l': str_arg_l += sprintf(
|
|
tmp + str_arg_l, f, ulong_arg);
|
|
break;
|
|
case 'L': str_arg_l += sprintf(
|
|
tmp + str_arg_l, f, ullong_arg);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// include the optional minus sign and possible
|
|
// "0x" in the region before the zero padding
|
|
// insertion point
|
|
if (zero_padding_insertion_ind < str_arg_l
|
|
&& tmp[zero_padding_insertion_ind] == '-')
|
|
zero_padding_insertion_ind++;
|
|
if (zero_padding_insertion_ind + 1 < str_arg_l
|
|
&& tmp[zero_padding_insertion_ind] == '0'
|
|
&& (tmp[zero_padding_insertion_ind + 1] == 'x'
|
|
|| tmp[zero_padding_insertion_ind + 1] == 'X'))
|
|
zero_padding_insertion_ind += 2;
|
|
}
|
|
|
|
{
|
|
size_t num_of_digits = str_arg_l
|
|
- zero_padding_insertion_ind;
|
|
|
|
if (alternate_form && fmt_spec == 'o'
|
|
// unless zero is already the first
|
|
// character
|
|
&& !(zero_padding_insertion_ind < str_arg_l
|
|
&& tmp[zero_padding_insertion_ind] == '0'))
|
|
{
|
|
// assure leading zero for alternate-form
|
|
// octal numbers
|
|
if (!precision_specified
|
|
|| precision < num_of_digits + 1)
|
|
{
|
|
// precision is increased to force the
|
|
// first character to be zero, except if a
|
|
// zero value is formatted with an
|
|
// explicit precision of zero
|
|
precision = num_of_digits + 1;
|
|
}
|
|
}
|
|
// zero padding to specified precision?
|
|
if (num_of_digits < precision)
|
|
number_of_zeros_to_pad = precision - num_of_digits;
|
|
}
|
|
// zero padding to specified minimal field width?
|
|
if (!justify_left && zero_padding)
|
|
{
|
|
int n = (int)(min_field_width - (str_arg_l
|
|
+ number_of_zeros_to_pad));
|
|
if (n > 0)
|
|
number_of_zeros_to_pad += n;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 'f':
|
|
case 'F':
|
|
case 'e':
|
|
case 'E':
|
|
case 'g':
|
|
case 'G':
|
|
{
|
|
// Floating point.
|
|
double f;
|
|
double abs_f;
|
|
char format[40];
|
|
int l;
|
|
int remove_trailing_zeroes = FALSE;
|
|
|
|
f =
|
|
# if defined(FEAT_EVAL)
|
|
tvs != NULL ? tv_float(tvs, &arg_idx) :
|
|
# endif
|
|
(skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
|
|
&arg_cur, fmt),
|
|
va_arg(ap, double));
|
|
|
|
abs_f = f < 0 ? -f : f;
|
|
|
|
if (fmt_spec == 'g' || fmt_spec == 'G')
|
|
{
|
|
// Would be nice to use %g directly, but it prints
|
|
// "1.0" as "1", we don't want that.
|
|
if ((abs_f >= 0.001 && abs_f < 10000000.0)
|
|
|| abs_f == 0.0)
|
|
fmt_spec = ASCII_ISUPPER(fmt_spec) ? 'F' : 'f';
|
|
else
|
|
fmt_spec = fmt_spec == 'g' ? 'e' : 'E';
|
|
remove_trailing_zeroes = TRUE;
|
|
}
|
|
|
|
if ((fmt_spec == 'f' || fmt_spec == 'F') &&
|
|
# ifdef VAX
|
|
abs_f > 1.0e38
|
|
# else
|
|
abs_f > 1.0e307
|
|
# endif
|
|
)
|
|
{
|
|
// Avoid a buffer overflow
|
|
STRCPY(tmp, infinity_str(f > 0.0, fmt_spec,
|
|
force_sign, space_for_positive));
|
|
str_arg_l = STRLEN(tmp);
|
|
zero_padding = 0;
|
|
}
|
|
else
|
|
{
|
|
if (isnan(f))
|
|
{
|
|
// Not a number: nan or NAN
|
|
STRCPY(tmp, ASCII_ISUPPER(fmt_spec) ? "NAN"
|
|
: "nan");
|
|
str_arg_l = 3;
|
|
zero_padding = 0;
|
|
}
|
|
else if (isinf(f))
|
|
{
|
|
STRCPY(tmp, infinity_str(f > 0.0, fmt_spec,
|
|
force_sign, space_for_positive));
|
|
str_arg_l = STRLEN(tmp);
|
|
zero_padding = 0;
|
|
}
|
|
else
|
|
{
|
|
// Regular float number
|
|
format[0] = '%';
|
|
l = 1;
|
|
if (force_sign)
|
|
format[l++] = space_for_positive ? ' ' : '+';
|
|
if (precision_specified)
|
|
{
|
|
size_t max_prec = TMP_LEN - 10;
|
|
|
|
// Make sure we don't get more digits than we
|
|
// have room for.
|
|
if ((fmt_spec == 'f' || fmt_spec == 'F')
|
|
&& abs_f > 1.0)
|
|
max_prec -= (size_t)log10(abs_f);
|
|
if (precision > max_prec)
|
|
precision = max_prec;
|
|
l += sprintf(format + l, ".%d", (int)precision);
|
|
}
|
|
format[l] = fmt_spec == 'F' ? 'f' : fmt_spec;
|
|
format[l + 1] = NUL;
|
|
|
|
str_arg_l = sprintf(tmp, format, f);
|
|
}
|
|
|
|
if (remove_trailing_zeroes)
|
|
{
|
|
int i;
|
|
char *tp;
|
|
|
|
// Using %g or %G: remove superfluous zeroes.
|
|
if (fmt_spec == 'f' || fmt_spec == 'F')
|
|
tp = tmp + str_arg_l - 1;
|
|
else
|
|
{
|
|
tp = (char *)vim_strchr((char_u *)tmp,
|
|
fmt_spec == 'e' ? 'e' : 'E');
|
|
if (tp != NULL)
|
|
{
|
|
// Remove superfluous '+' and leading
|
|
// zeroes from the exponent.
|
|
if (tp[1] == '+')
|
|
{
|
|
// Change "1.0e+07" to "1.0e07"
|
|
STRMOVE(tp + 1, tp + 2);
|
|
--str_arg_l;
|
|
}
|
|
i = (tp[1] == '-') ? 2 : 1;
|
|
while (tp[i] == '0')
|
|
{
|
|
// Change "1.0e07" to "1.0e7"
|
|
STRMOVE(tp + i, tp + i + 1);
|
|
--str_arg_l;
|
|
}
|
|
--tp;
|
|
}
|
|
}
|
|
|
|
if (tp != NULL && !precision_specified)
|
|
// Remove trailing zeroes, but keep the one
|
|
// just after a dot.
|
|
while (tp > tmp + 2 && *tp == '0'
|
|
&& tp[-1] != '.')
|
|
{
|
|
STRMOVE(tp, tp + 1);
|
|
--tp;
|
|
--str_arg_l;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
char *tp;
|
|
|
|
// Be consistent: some printf("%e") use 1.0e+12
|
|
// and some 1.0e+012. Remove one zero in the last
|
|
// case.
|
|
tp = (char *)vim_strchr((char_u *)tmp,
|
|
fmt_spec == 'e' ? 'e' : 'E');
|
|
if (tp != NULL && (tp[1] == '+' || tp[1] == '-')
|
|
&& tp[2] == '0'
|
|
&& vim_isdigit(tp[3])
|
|
&& vim_isdigit(tp[4]))
|
|
{
|
|
STRMOVE(tp + 2, tp + 3);
|
|
--str_arg_l;
|
|
}
|
|
}
|
|
}
|
|
if (zero_padding && min_field_width > str_arg_l
|
|
&& (tmp[0] == '-' || force_sign))
|
|
{
|
|
// padding 0's should be inserted after the sign
|
|
number_of_zeros_to_pad = min_field_width - str_arg_l;
|
|
zero_padding_insertion_ind = 1;
|
|
}
|
|
str_arg = tmp;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
// unrecognized conversion specifier, keep format string
|
|
// as-is
|
|
zero_padding = 0; // turn zero padding off for non-numeric
|
|
// conversion
|
|
justify_left = 1;
|
|
min_field_width = 0; // reset flags
|
|
|
|
// discard the unrecognized conversion, just keep *
|
|
// the unrecognized conversion character
|
|
str_arg = p;
|
|
str_arg_l = 0;
|
|
if (*p != NUL)
|
|
str_arg_l++; // include invalid conversion specifier
|
|
// unchanged if not at end-of-string
|
|
break;
|
|
}
|
|
|
|
if (*p != NUL)
|
|
p++; // step over the just processed conversion specifier
|
|
|
|
// insert padding to the left as requested by min_field_width;
|
|
// this does not include the zero padding in case of numerical
|
|
// conversions
|
|
if (!justify_left)
|
|
{
|
|
// left padding with blank or zero
|
|
int pn = (int)(min_field_width - (str_arg_l + number_of_zeros_to_pad));
|
|
|
|
if (pn > 0)
|
|
{
|
|
if (str_l < str_m)
|
|
{
|
|
size_t avail = str_m - str_l;
|
|
|
|
vim_memset(str + str_l, zero_padding ? '0' : ' ',
|
|
(size_t)pn > avail ? avail
|
|
: (size_t)pn);
|
|
}
|
|
str_l += pn;
|
|
}
|
|
}
|
|
|
|
// zero padding as requested by the precision or by the minimal
|
|
// field width for numeric conversions required?
|
|
if (number_of_zeros_to_pad == 0)
|
|
{
|
|
// will not copy first part of numeric right now, *
|
|
// force it to be copied later in its entirety
|
|
zero_padding_insertion_ind = 0;
|
|
}
|
|
else
|
|
{
|
|
// insert first part of numerics (sign or '0x') before zero
|
|
// padding
|
|
int zn = (int)zero_padding_insertion_ind;
|
|
|
|
if (zn > 0)
|
|
{
|
|
if (str_l < str_m)
|
|
{
|
|
size_t avail = str_m - str_l;
|
|
|
|
mch_memmove(str + str_l, str_arg,
|
|
(size_t)zn > avail ? avail
|
|
: (size_t)zn);
|
|
}
|
|
str_l += zn;
|
|
}
|
|
|
|
// insert zero padding as requested by the precision or min
|
|
// field width
|
|
zn = (int)number_of_zeros_to_pad;
|
|
if (zn > 0)
|
|
{
|
|
if (str_l < str_m)
|
|
{
|
|
size_t avail = str_m - str_l;
|
|
|
|
vim_memset(str + str_l, '0',
|
|
(size_t)zn > avail ? avail
|
|
: (size_t)zn);
|
|
}
|
|
str_l += zn;
|
|
}
|
|
}
|
|
|
|
// insert formatted string
|
|
// (or as-is conversion specifier for unknown conversions)
|
|
{
|
|
int sn = (int)(str_arg_l - zero_padding_insertion_ind);
|
|
|
|
if (sn > 0)
|
|
{
|
|
if (str_l < str_m)
|
|
{
|
|
size_t avail = str_m - str_l;
|
|
|
|
mch_memmove(str + str_l,
|
|
str_arg + zero_padding_insertion_ind,
|
|
(size_t)sn > avail ? avail : (size_t)sn);
|
|
}
|
|
str_l += sn;
|
|
}
|
|
}
|
|
|
|
// insert right padding
|
|
if (justify_left)
|
|
{
|
|
// right blank padding to the field width
|
|
int pn = (int)(min_field_width
|
|
- (str_arg_l + number_of_zeros_to_pad));
|
|
|
|
if (pn > 0)
|
|
{
|
|
if (str_l < str_m)
|
|
{
|
|
size_t avail = str_m - str_l;
|
|
|
|
vim_memset(str + str_l, ' ',
|
|
(size_t)pn > avail ? avail
|
|
: (size_t)pn);
|
|
}
|
|
str_l += pn;
|
|
}
|
|
}
|
|
vim_free(tofree);
|
|
}
|
|
}
|
|
|
|
if (str_m > 0)
|
|
{
|
|
// make sure the string is nul-terminated even at the expense of
|
|
// overwriting the last character (shouldn't happen, but just in case)
|
|
//
|
|
str[str_l <= str_m - 1 ? str_l : str_m - 1] = '\0';
|
|
}
|
|
|
|
if (tvs != NULL && tvs[num_posarg != 0 ? num_posarg : arg_idx - 1].v_type != VAR_UNKNOWN)
|
|
emsg(_(e_too_many_arguments_to_printf));
|
|
|
|
error:
|
|
vim_free((char*)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.
|
|
return (int)str_l;
|
|
}
|
|
|
|
#endif // PROTO
|