Mercurial > vim
diff src/strings.c @ 32670:695b50472e85
Fix line endings issue
author | Christian Brabandt <cb@256bit.org> |
---|---|
date | Mon, 26 Jun 2023 13:13:12 +0200 |
parents | 448aef880252 |
children | 2d5e8c46508b |
line wrap: on
line diff
--- a/src/strings.c +++ b/src/strings.c @@ -1,3155 +1,3155 @@ -/* 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; - int 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 - while (--l >= 0) // copy the var - *d++ = *p++; - 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 - -/* - * 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(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; -} - -/* - * Reverse the string in 'str' and set the result in 'rettv'. - */ - void -string_reverse(char_u *str, typval_T *rettv) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - if (str == NULL) - return; - - char_u *rstr = vim_strsave(str); - rettv->vval.v_string = rstr; - if (rstr == NULL || *str == NUL) - return; - - size_t len = STRLEN(rstr); - if (has_mbyte) - { - char_u *src = str; - char_u *dest = rstr + len; - - while (src < str + len) - { - int clen = mb_ptr2len(src); - dest -= clen; - mch_memmove(dest, src, (size_t)clen); - src += clen; - } - } - else - { - for (size_t i = 0; i < len / 2; i++) - { - char tmp = rstr[len - i - 1]; - rstr[len - i - 1] = rstr[i]; - rstr[i] = tmp; - } - } -} - -/* - * 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); - - newtv.v_type = VAR_UNKNOWN; - 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; - } - else if (filtermap != FILTERMAP_FILTER) - { - 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 (!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, 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 UNUSED) -{ - 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; -} - -/* - * "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 (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); -} - - int -vim_vsnprintf_typval( - char *str, - size_t str_m, - const char *fmt, - va_list ap, - typval_T *tvs) -{ - size_t str_l = 0; - const char *p = fmt; - int arg_idx = 1; - - 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; - - - p++; // skip '%' - - // 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; - - p++; - j = -# if defined(FEAT_EVAL) - tvs != NULL ? tv_nr(tvs, &arg_idx) : -# endif - va_arg(ap, int); - 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 - unsigned int uj = *p++ - '0'; - - while (VIM_ISDIGIT((int)(*p))) - uj = 10 * uj + (unsigned int)(*p++ - '0'); - min_field_width = uj; - } - - // parse precision - if (*p == '.') - { - p++; - precision_specified = 1; - if (*p == '*') - { - int j; - - j = -# if defined(FEAT_EVAL) - tvs != NULL ? tv_nr(tvs, &arg_idx) : -# endif - va_arg(ap, int); - p++; - if (j >= 0) - precision = j; - else - { - precision_specified = 0; - precision = 0; - } - } - else if (VIM_ISDIGIT((int)(*p))) - { - // size_t could be wider than unsigned int; make sure we - // treat argument like common implementations do - unsigned int uj = *p++ - '0'; - - while (VIM_ISDIGIT((int)(*p))) - uj = 10 * uj + (unsigned int)(*p++ - '0'); - precision = uj; - } - } - - // 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 - - // 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 - 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 - 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 - { - // Don't put the #if inside memchr(), it can be a - // macro. - // 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 - 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 - 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 - 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 - 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 - 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 - 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 - 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 - 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 - 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[arg_idx - 1].v_type != VAR_UNKNOWN) - emsg(_(e_too_many_arguments_to_printf)); - - // 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 +/* 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; + int 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 + while (--l >= 0) // copy the var + *d++ = *p++; + 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 + +/* + * 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(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; +} + +/* + * Reverse the string in 'str' and set the result in 'rettv'. + */ + void +string_reverse(char_u *str, typval_T *rettv) +{ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + if (str == NULL) + return; + + char_u *rstr = vim_strsave(str); + rettv->vval.v_string = rstr; + if (rstr == NULL || *str == NUL) + return; + + size_t len = STRLEN(rstr); + if (has_mbyte) + { + char_u *src = str; + char_u *dest = rstr + len; + + while (src < str + len) + { + int clen = mb_ptr2len(src); + dest -= clen; + mch_memmove(dest, src, (size_t)clen); + src += clen; + } + } + else + { + for (size_t i = 0; i < len / 2; i++) + { + char tmp = rstr[len - i - 1]; + rstr[len - i - 1] = rstr[i]; + rstr[i] = tmp; + } + } +} + +/* + * 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); + + newtv.v_type = VAR_UNKNOWN; + 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; + } + else if (filtermap != FILTERMAP_FILTER) + { + 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 (!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, 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 UNUSED) +{ + 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; +} + +/* + * "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 (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); +} + + int +vim_vsnprintf_typval( + char *str, + size_t str_m, + const char *fmt, + va_list ap, + typval_T *tvs) +{ + size_t str_l = 0; + const char *p = fmt; + int arg_idx = 1; + + 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; + + + p++; // skip '%' + + // 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; + + p++; + j = +# if defined(FEAT_EVAL) + tvs != NULL ? tv_nr(tvs, &arg_idx) : +# endif + va_arg(ap, int); + 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 + unsigned int uj = *p++ - '0'; + + while (VIM_ISDIGIT((int)(*p))) + uj = 10 * uj + (unsigned int)(*p++ - '0'); + min_field_width = uj; + } + + // parse precision + if (*p == '.') + { + p++; + precision_specified = 1; + if (*p == '*') + { + int j; + + j = +# if defined(FEAT_EVAL) + tvs != NULL ? tv_nr(tvs, &arg_idx) : +# endif + va_arg(ap, int); + p++; + if (j >= 0) + precision = j; + else + { + precision_specified = 0; + precision = 0; + } + } + else if (VIM_ISDIGIT((int)(*p))) + { + // size_t could be wider than unsigned int; make sure we + // treat argument like common implementations do + unsigned int uj = *p++ - '0'; + + while (VIM_ISDIGIT((int)(*p))) + uj = 10 * uj + (unsigned int)(*p++ - '0'); + precision = uj; + } + } + + // 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 + + // 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 + 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 + 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 + { + // Don't put the #if inside memchr(), it can be a + // macro. + // 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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[arg_idx - 1].v_type != VAR_UNKNOWN) + emsg(_(e_too_many_arguments_to_printf)); + + // 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