summaryrefslogtreecommitdiff
path: root/mnv/src/strings.c
diff options
context:
space:
mode:
Diffstat (limited to 'mnv/src/strings.c')
-rw-r--r--mnv/src/strings.c4624
1 files changed, 4624 insertions, 0 deletions
diff --git a/mnv/src/strings.c b/mnv/src/strings.c
new file mode 100644
index 0000000000..7fd1987d08
--- /dev/null
+++ b/mnv/src/strings.c
@@ -0,0 +1,4624 @@
+/* vi:set ts=8 sts=4 sw=4 noet:
+ *
+ * MNV - MNV is not Vim by Bram Moolenaar
+ *
+ * Do ":help uganda" in MNV to read copying and usage conditions.
+ * Do ":help credits" in MNV to see a list of people who contributed.
+ * See README.txt for an overview of the MNV source code.
+ */
+
+/*
+ * strings.c: string manipulation functions
+ */
+
+#define USING_FLOAT_STUFF
+#include "mnv.h"
+
+/*
+ * Copy "string" into newly allocated memory.
+ */
+ char_u *
+mnv_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 *
+mnv_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 mnv_strsave(), but any characters found in esc_chars are preceded
+ * by a backslash.
+ */
+ char_u *
+mnv_strsave_escaped(char_u *string, char_u *esc_chars)
+{
+ return mnv_strsave_escaped_ext(string, esc_chars, '\\', FALSE);
+}
+
+/*
+ * Same as mnv_strsave_escaped(), but when "bsl" is TRUE also escape
+ * characters where rem_backslash() would remove the backslash.
+ * Escape the characters with "cc".
+ */
+ char_u *
+mnv_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 (mnv_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 (mnv_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 *
+mnv_strsave_shellescape(char_u *string, int do_special, int do_newline)
+{
+ unsigned length;
+ char_u *p;
+ char_u *d;
+ char_u *escaped_string;
+ size_t l;
+ int csh_like;
+ int fish_like;
+ char_u *shname;
+ int powershell;
+#ifdef MSWIN
+ int double_quotes;
+#endif
+
+ // Only csh and similar shells expand '!' within single quotes. For sh and
+ // the like we must not put a backslash before it, it will be taken
+ // literally. If do_special is set the '!' will be escaped twice.
+ // Csh also needs to have "\n" escaped twice when do_special is set.
+ csh_like = csh_like_shell();
+
+ // Fish shell uses '\' as an escape character within single quotes, so '\'
+ // itself must be escaped to get a literal '\'.
+ fish_like = fish_like_shell();
+
+ // PowerShell uses its own version for quoting single quotes
+ shname = gettail(p_sh);
+ powershell = strstr((char *)shname, "pwsh") != NULL;
+#ifdef MSWIN
+ powershell = powershell || strstr((char *)shname, "powershell") != NULL;
+ // PowerShell only accepts single quotes so override shellslash.
+ double_quotes = !powershell && !p_ssl;
+#endif
+
+ // First count the number of extra bytes required.
+ length = (unsigned)STRLEN(string) + 3; // two quotes and a trailing NUL
+ for (p = string; *p != NUL; MB_PTR_ADV(p))
+ {
+#ifdef MSWIN
+ if (double_quotes)
+ {
+ if (*p == '"')
+ ++length; // " -> ""
+ }
+ else
+#endif
+ if (*p == '\'')
+ {
+ if (powershell)
+ length += 2; // ' => ''
+ else
+ length += 3; // ' => '\''
+ }
+ if ((*p == '\n' && (csh_like || do_newline))
+ || (*p == '!' && (csh_like || do_special)))
+ {
+ ++length; // insert backslash
+ if (csh_like && do_special)
+ ++length; // insert backslash
+ }
+ if (do_special && find_cmdline_var(p, &l) >= 0)
+ {
+ ++length; // insert backslash
+ p += l - 1;
+ }
+ if (*p == '\\' && fish_like)
+ ++length; // insert backslash
+ }
+
+ // Allocate memory for the result and fill it.
+ escaped_string = alloc(length);
+ if (escaped_string != NULL)
+ {
+ d = escaped_string;
+
+ // add opening quote
+#ifdef MSWIN
+ if (double_quotes)
+ *d++ = '"';
+ else
+#endif
+ *d++ = '\'';
+
+ for (p = string; *p != NUL; )
+ {
+#ifdef MSWIN
+ if (double_quotes)
+ {
+ if (*p == '"')
+ {
+ *d++ = '"';
+ *d++ = '"';
+ ++p;
+ continue;
+ }
+ }
+ else
+#endif
+ if (*p == '\'')
+ {
+ if (powershell)
+ {
+ *d++ = '\'';
+ *d++ = '\'';
+ }
+ else
+ {
+ *d++ = '\'';
+ *d++ = '\\';
+ *d++ = '\'';
+ *d++ = '\'';
+ }
+ ++p;
+ continue;
+ }
+ if ((*p == '\n' && (csh_like || do_newline))
+ || (*p == '!' && (csh_like || do_special)))
+ {
+ *d++ = '\\';
+ if (csh_like && do_special)
+ *d++ = '\\';
+ *d++ = *p++;
+ continue;
+ }
+ if (do_special && find_cmdline_var(p, &l) >= 0)
+ {
+ *d++ = '\\'; // insert backslash
+ memcpy(d, p, l); // copy the var
+ d += l;
+ p += l;
+ continue;
+ }
+ if (*p == '\\' && fish_like)
+ {
+ *d++ = '\\';
+ *d++ = *p++;
+ continue;
+ }
+
+ MB_COPY_CHAR(p, d);
+ }
+
+ // add terminating quote and finish with a NUL
+#ifdef MSWIN
+ if (double_quotes)
+ *d++ = '"';
+ else
+#endif
+ *d++ = '\'';
+ *d = NUL;
+ }
+
+ return escaped_string;
+}
+
+/*
+ * Like mnv_strsave(), but make all characters uppercase.
+ * This uses ASCII lower-to-upper case translation, language independent.
+ */
+ char_u *
+mnv_strsave_up(char_u *string)
+{
+ char_u *p1;
+
+ p1 = mnv_strsave(string);
+ mnv_strup(p1);
+ return p1;
+}
+
+/*
+ * Like mnv_strnsave(), but make all characters uppercase.
+ * This uses ASCII lower-to-upper case translation, language independent.
+ */
+ char_u *
+mnv_strnsave_up(char_u *string, size_t len)
+{
+ char_u *p1;
+
+ p1 = mnv_strnsave(string, len);
+ mnv_strup(p1);
+ return p1;
+}
+
+/*
+ * ASCII lower-to-upper case translation, language independent.
+ */
+ void
+mnv_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)
+/*
+ * 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 = mnv_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)
+ {
+ mnv_free(res);
+ return NULL;
+ }
+ mch_memmove(s, res, p - res);
+ STRCPY(s + (p - res) + newl, p + l);
+ p = s + (p - res);
+ mnv_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 = mnv_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)
+ {
+ mnv_free(res);
+ return NULL;
+ }
+ mch_memmove(s, res, p - res);
+ STRCPY(s + (p - res) + newl, p + l);
+ p = s + (p - res);
+ mnv_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 && MNV_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
+mnv_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
+mnv_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
+mnv_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)
+/*
+ * 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
+mnv_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)
+/*
+ * 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
+mnv_strnicmp(char *s1, char *s2, size_t len)
+{
+ int i;
+
+ while (len > 0)
+ {
+ i = (int)TOLOWER_LOC(*s1) - (int)TOLOWER_LOC(*s2);
+ if (i != 0)
+ return i; // this character different
+ if (*s1 == NUL)
+ break; // strings match until NUL
+ ++s1;
+ ++s2;
+ --len;
+ }
+ return 0; // strings match
+}
+#endif
+
+/*
+ * Compare two ASCII strings, for length "len", ignoring case, ignoring locale
+ * (mostly matters for turkish locale where i I might be different).
+ * return 0 for match, < 0 for smaller, > 0 for bigger
+ */
+ int
+mnv_strnicmp_asc(char *s1, char *s2, size_t len)
+{
+ int i = 0;
+
+ while (len > 0)
+ {
+ i = TOLOWER_ASC(*s1) - TOLOWER_ASC(*s2);
+ if (i != 0)
+ break; // this character is different
+ if (*s1 == NUL)
+ break; // strings match until NUL
+ ++s1;
+ ++s2;
+ --len;
+ }
+ return i;
+}
+
+/*
+ * Search for first occurrence of "c" in "string".
+ * Version of strchr() that handles unsigned char strings with characters from
+ * 128 to 255 correctly. It also doesn't return a pointer to the NUL at the
+ * end of the string.
+ */
+ char_u *
+mnv_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 *
+mnv_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 *
+mnv_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;
+}
+
+/*
+ * MNV'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 mnv_strpbrk
+# undef mnv_strpbrk
+# endif
+ char_u *
+mnv_strpbrk(char_u *s, char_u *charset)
+{
+ while (*s)
+ {
+ if (mnv_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)
+/*
+ * Return TRUE if string "s" contains a non-ASCII character (128 or higher).
+ * When "s" is NULL FALSE is returned.
+ */
+ int
+has_non_ascii(char_u *s)
+{
+ char_u *p;
+
+ if (s != NULL)
+ for (p = s; *p != NUL; ++p)
+ if (*p >= 128)
+ return TRUE;
+ return FALSE;
+}
+#endif
+
+/*
+ * Concatenate two strings and return the result in allocated memory.
+ * Returns NULL when out of memory.
+ */
+ char_u *
+concat_str(char_u *str1, char_u *str2)
+{
+ char_u *dest;
+ size_t l = str1 == NULL ? 0 : STRLEN(str1);
+
+ dest = alloc(l + (str2 == NULL ? 0 : STRLEN(str2)) + 1L);
+ if (dest == NULL)
+ return NULL;
+ if (str1 == NULL)
+ *dest = NUL;
+ else
+ STRCPY(dest, str1);
+ if (str2 != NULL)
+ STRCPY(dest + l, str2);
+ return dest;
+}
+
+#if defined(FEAT_EVAL) || defined(FEAT_RIGHTLEFT)
+/*
+ * Reverse text into allocated memory.
+ * Returns the allocated string, NULL when out of memory.
+ */
+ char_u *
+reverse_text(char_u *s)
+{
+ size_t len = STRLEN(s);
+ char_u *rev = alloc(len + 1);
+ if (rev == NULL)
+ return NULL;
+
+ for (size_t s_i = 0, rev_i = len; s_i < len; ++s_i)
+ {
+ if (has_mbyte)
+ {
+ int mb_len = (*mb_ptr2len)(s + s_i);
+ rev_i -= mb_len;
+ mch_memmove(rev + rev_i, s + s_i, mb_len);
+ s_i += mb_len - 1;
+ }
+ else
+ rev[--rev_i] = s[s_i];
+ }
+ rev[len] = NUL;
+ return rev;
+}
+#endif
+
+#if defined(FEAT_EVAL)
+/*
+ * 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;
+
+ size_t needlelen = STRLEN(needle);
+ if (ic)
+ {
+ while (*p != NUL)
+ {
+ if (MB_STRNICMP(p, needle, needlelen) == 0)
+ {
+ ++n;
+ p += needlelen;
+ }
+ else
+ MB_PTR_ADV(p);
+ }
+ }
+ else
+ while ((next = (char_u *)strstr((char *)p, (char *)needle)) != NULL)
+ {
+ ++n;
+ p = next + needlelen;
+ }
+
+ return n;
+}
+
+/*
+ * Make a typval_T of the first character of "input" and store it in "output".
+ * Return -1 on failure or v_string's length on success.
+ */
+ 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 -1;
+
+ len = has_mbyte ? mb_ptr2len(input) : 1;
+ STRNCPY(buf, input, len);
+ buf[len] = NUL;
+ output->v_type = VAR_STRING;
+ output->vval.v_string = mnv_strnsave(buf, len);
+
+ return output->vval.v_string == NULL ? -1 : len;
+}
+
+/*
+ * 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_mnv_var_nr() doesn't set the type
+ set_mnv_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)
+ {
+ len = copy_first_char_to_tv(p, &tv);
+ if (len < 0)
+ break;
+
+ set_mnv_var_nr(VV_KEY, idx);
+ if (filter_map_one(&tv, expr, filtermap, fc, &newtv, &rem) == FAIL
+ || did_emsg)
+ {
+ clear_tv(&newtv);
+ clear_tv(&tv);
+ break;
+ }
+ if (filtermap == FILTERMAP_MAP || filtermap == FILTERMAP_MAPNEW)
+ {
+ if (newtv.v_type != VAR_STRING)
+ {
+ clear_tv(&newtv);
+ clear_tv(&tv);
+ emsg(_(e_string_required));
+ break;
+ }
+ else
+ ga_concat(&ga, newtv.vval.v_string);
+ }
+ else if (filtermap == FILTERMAP_FOREACH || !rem)
+ ga_concat(&ga, tv.vval.v_string);
+
+ clear_tv(&newtv);
+ clear_tv(&tv);
+
+ ++idx;
+ }
+ ga_append(&ga, NUL);
+ rettv->vval.v_string = ga.ga_data;
+ if (fc != NULL)
+ remove_funccal();
+}
+
+/*
+ * Implementation of reduce() for String "argvars[0]" using the function "expr"
+ * starting with the optional initial value "argvars[2]" and return the result
+ * in "rettv".
+ */
+ void
+string_reduce(
+ typval_T *argvars,
+ typval_T *expr,
+ typval_T *rettv)
+{
+ char_u *p = tv_get_string(&argvars[0]);
+ int len;
+ typval_T argv[3];
+ int r;
+ int called_emsg_start = called_emsg;
+ funccall_T *fc;
+
+ if (argvars[2].v_type == VAR_UNKNOWN)
+ {
+ if (*p == NUL)
+ {
+ semsg(_(e_reduce_of_an_empty_str_with_no_initial_value), "String");
+ return;
+ }
+ len = copy_first_char_to_tv(p, rettv);
+ if (len < 0)
+ return;
+ p += len;
+ }
+ 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;
+ len = copy_first_char_to_tv(p, &argv[1]);
+ if (len < 0)
+ break;
+
+ r = eval_expr_typval(expr, TRUE, argv, 2, fc, rettv);
+
+ clear_tv(&argv[0]);
+ clear_tv(&argv[1]);
+ if (r == FAIL || called_emsg != called_emsg_start)
+ return;
+ }
+
+ if (fc != NULL)
+ remove_funccal();
+}
+
+/*
+ * Implementation of "byteidx()" and "byteidxcomp()" functions
+ */
+ static void
+byteidx_common(typval_T *argvars, typval_T *rettv, int comp)
+{
+ rettv->vval.v_number = -1;
+
+ if (in_mnv9script()
+ && (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 += clen;
+ }
+ else if (idx > 0)
+ t += ptr2len(t);
+ }
+ rettv->vval.v_number = (varnumber_T)(t - str);
+}
+
+/*
+ * "byteidx()" function
+ */
+ void
+f_byteidx(typval_T *argvars, typval_T *rettv)
+{
+ byteidx_common(argvars, rettv, FALSE);
+}
+
+/*
+ * "byteidxcomp()" function
+ */
+ void
+f_byteidxcomp(typval_T *argvars, typval_T *rettv)
+{
+ byteidx_common(argvars, rettv, TRUE);
+}
+
+/*
+ * "charidx()" function
+ */
+ void
+f_charidx(typval_T *argvars, typval_T *rettv)
+{
+ rettv->vval.v_number = -1;
+
+ if (check_for_string_arg(argvars, 0) == FAIL
+ || check_for_number_arg(argvars, 1) == FAIL
+ || check_for_opt_bool_arg(argvars, 2) == FAIL
+ || (argvars[2].v_type != VAR_UNKNOWN
+ && check_for_opt_bool_arg(argvars, 3) == FAIL))
+ return;
+
+ char_u *str = tv_get_string_chk(&argvars[0]);
+ varnumber_T idx = tv_get_number_chk(&argvars[1], NULL);
+ if (str == NULL || idx < 0)
+ return;
+
+ varnumber_T countcc = FALSE;
+ varnumber_T utf16idx = FALSE;
+ if (argvars[2].v_type != VAR_UNKNOWN)
+ {
+ countcc = tv_get_bool(&argvars[2]);
+ if (argvars[3].v_type != VAR_UNKNOWN)
+ utf16idx = tv_get_bool(&argvars[3]);
+ }
+
+ int (*ptr2len)(char_u *);
+ if (enc_utf8 && countcc)
+ ptr2len = utf_ptr2len;
+ else
+ ptr2len = mb_ptr2len;
+
+ char_u *p;
+ int len;
+ for (p = str, len = 0; utf16idx ? idx >= 0 : p <= str + idx; len++)
+ {
+ if (*p == NUL)
+ {
+ // If the index is exactly the number of bytes or utf-16 code units
+ // in the string then return the length of the string in
+ // characters.
+ if (utf16idx ? (idx == 0) : (p == (str + idx)))
+ rettv->vval.v_number = len;
+ return;
+ }
+ if (utf16idx)
+ {
+ idx--;
+ int clen = ptr2len(p);
+ int c = (clen > 1) ? utf_ptr2char(p) : *p;
+ if (c > 0xFFFF)
+ idx--;
+ }
+ p += ptr2len(p);
+ }
+
+ rettv->vval.v_number = len > 0 ? len - 1 : 0;
+}
+
+/*
+ * Convert the string "str", from encoding "from" to encoding "to".
+ */
+ static int
+convert_string(string_T *str, char_u *from, char_u *to, string_T *ret)
+{
+ mnvconv_T mnvconv;
+
+ mnvconv.vc_type = CONV_NONE;
+ if (convert_setup(&mnvconv, from, to) == FAIL)
+ return FAIL;
+ mnvconv.vc_fail = TRUE;
+ if (mnvconv.vc_type == CONV_NONE)
+ {
+ ret->string = mnv_strnsave(str->string, str->length);
+ if (ret->string == NULL)
+ ret->length = 0;
+ else
+ ret->length = str->length;
+ }
+ else
+ {
+ int len = (int)str->length;
+ ret->string = string_convert(&mnvconv, str->string, &len);
+ ret->length = len;
+ }
+ convert_setup(&mnvconv, NULL, NULL);
+
+ return (ret->string == NULL) ? FAIL : OK;
+}
+
+/*
+ * Add the bytes from "str" to "blob".
+ */
+ static void
+blob_from_string(char_u *str, blob_T *blob)
+{
+ int len = (int)STRLEN(str);
+ char_u *dest;
+
+ if (len == 0)
+ return;
+ if (ga_grow(&blob->bv_ga, len) == FAIL)
+ return;
+ dest = (char_u *)blob->bv_ga.ga_data + blob->bv_ga.ga_len;
+ mch_memmove(dest, str, (size_t)len);
+ // Translate newlines in the string to NUL characters
+ for (int i = 0; i < len; ++i)
+ if (dest[i] == NL)
+ dest[i] = NUL;
+ blob->bv_ga.ga_len += len;
+}
+
+/*
+ * Return a string created from the bytes in blob starting at "start_idx".
+ * A NL character in the blob indicates end of string.
+ * A NUL character in the blob is translated to a NL.
+ * If a newline is followed by another newline (empty line), then an empty
+ * allocated string is returned and "start_idx" is moved forward by one byte.
+ * On return, "start_idx" points to next byte to process in blob.
+ */
+ static int
+string_from_blob(blob_T *blob, long *start_idx, string_T *ret)
+{
+ garray_T str_ga;
+ long blen;
+ int idx;
+
+ ga_init2(&str_ga, sizeof(char), 80);
+
+ blen = blob_len(blob);
+
+ for (idx = *start_idx; idx < blen; idx++)
+ {
+ char_u byte = (char_u)blob_get(blob, idx);
+ if (byte == NL)
+ {
+ idx++;
+ break;
+ }
+
+ if (byte == NUL)
+ byte = NL;
+
+ ga_append(&str_ga, byte);
+ }
+
+ if (str_ga.ga_data != NULL)
+ {
+ ret->string = mnv_strnsave(str_ga.ga_data, str_ga.ga_len);
+ ret->length = str_ga.ga_len;
+ }
+ else
+ {
+ ret->string = mnv_strsave((char_u *)"");
+ ret->length = 0;
+ }
+ *start_idx = idx;
+
+ ga_clear(&str_ga);
+ return (ret->string == NULL) ? FAIL : OK;
+}
+
+/*
+ * Normalize encoding name for iconv by adding hyphens.
+ * For example: "ucs2be" -> "ucs-2be", "utf16le" -> "utf-16le"
+ * Returns allocated string or NULL on allocation failure.
+ */
+ static char_u *
+normalize_encoding_name(char_u *enc_skipped)
+{
+ char_u *from_encoding_raw = alloc(STRLEN(enc_skipped) + 3);
+ if (from_encoding_raw == NULL)
+ return NULL;
+
+ char_u *s = enc_skipped;
+ char_u *pe = from_encoding_raw;
+
+ // Convert to lowercase and replace '_' with '-'
+ while (*s != NUL)
+ {
+ if (*s == '_')
+ *pe++ = '-';
+ else
+ *pe++ = TOLOWER_ASC(*s);
+ ++s;
+ }
+ *pe = NUL;
+
+ // Add hyphen before digit: "ucs2be" -> "ucs-2be", "utf16le" -> "utf-16le"
+ char_u *p = from_encoding_raw;
+ if ((STRNCMP(p, "ucs", 3) == 0 && MNV_ISDIGIT(p[3]) && p[3] != NUL && p[4] != '-') ||
+ (STRNCMP(p, "utf", 3) == 0 && MNV_ISDIGIT(p[3]) && p[3] != NUL && p[4] != '-'))
+ {
+ // Insert hyphen after "ucs" or "utf": "ucs2" -> "ucs-2"
+ mch_memmove(p + 4, p + 3, STRLEN(p + 3) + 1);
+ p[3] = '-';
+ }
+
+ return from_encoding_raw;
+}
+
+/*
+ * "blob2str()" function
+ * Converts a blob to a string, ensuring valid UTF-8 encoding.
+ */
+ static void
+append_converted_string_to_list(
+ string_T *converted,
+ int validate_utf8,
+ list_T *list,
+ char_u *from_encoding)
+{
+ if (converted->string != NULL)
+ {
+ // After conversion, the output is a valid UTF-8 string (NUL-terminated)
+ // Split by newlines and add to list
+ char_u *p = converted->string;
+ char_u *end = converted->string + converted->length;
+ while (p < end)
+ {
+ string_T line;
+ char_u *line_start = p;
+
+ while (p < end && *p != NL)
+ p++;
+
+ // Add this line to the result list
+ line.length = (size_t)(p - line_start);
+ line.string = mnv_strnsave(line_start, line.length);
+ if (line.string != NULL)
+ {
+ if (validate_utf8 && !utf_valid_string(line.string, NULL))
+ {
+ mnv_free(line.string);
+ semsg(_(e_str_encoding_from_failed), p_enc);
+ mnv_free(converted->string);
+ return; // Stop processing
+ }
+ if (list_append_string(list, line.string, (int)line.length) == FAIL)
+ {
+ mnv_free(line.string);
+ mnv_free(converted->string);
+ return; // Stop processing on append failure
+ }
+ mnv_free(line.string);
+ }
+ else
+ {
+ // Allocation failure: report error and stop processing
+ emsg(_(e_out_of_memory));
+ mnv_free(converted->string);
+ return;
+ }
+
+ if (*p == NL)
+ p++;
+ }
+ mnv_free(converted->string);
+ }
+ else
+ {
+ semsg(_(e_str_encoding_from_failed), from_encoding);
+ }
+}
+
+ static int
+append_validated_line_to_list(string_T *line, int validate_utf8, list_T *list)
+{
+ if (validate_utf8 && !utf_valid_string(line->string, NULL))
+ {
+ semsg(_(e_str_encoding_from_failed), p_enc);
+ mnv_free(line->string);
+ return FAIL;
+ }
+
+ int ret = list_append_string(list, line->string, (int)line->length);
+ mnv_free(line->string);
+ return ret;
+}
+
+ void
+f_blob2str(typval_T *argvars, typval_T *rettv)
+{
+ blob_T *blob;
+ int blen;
+ long idx;
+ int validate_utf8 = FALSE;
+
+ if (check_for_blob_arg(argvars, 0) == FAIL
+ || check_for_opt_dict_arg(argvars, 1) == FAIL)
+ return;
+
+ if (rettv_list_alloc(rettv) == FAIL)
+ return;
+
+ blob = argvars->vval.v_blob;
+ if (blob == NULL)
+ return;
+ blen = blob_len(blob);
+
+ char_u *from_encoding = NULL;
+ char_u *from_encoding_raw = NULL; // Encoding name with endianness preserved for iconv
+ if (argvars[1].v_type != VAR_UNKNOWN)
+ {
+ dict_T *d = argvars[1].vval.v_dict;
+ if (d != NULL)
+ {
+ char_u *enc = dict_get_string(d, "encoding", FALSE);
+ if (enc != NULL)
+ {
+ char_u *enc_skipped = enc_skip(enc);
+ from_encoding = enc_canonize(enc_skipped);
+
+ // For iconv, preserve the endianness suffix by creating a normalized
+ // version with hyphens: "ucs2be" -> "ucs-2be", "utf16le" -> "utf-16le"
+ from_encoding_raw = normalize_encoding_name(enc_skipped);
+ if (from_encoding_raw == NULL)
+ {
+ emsg(_(e_out_of_memory));
+ MNV_CLEAR(from_encoding);
+ return;
+ }
+ }
+ }
+ }
+
+ if (STRCMP(p_enc, "utf-8") == 0 || STRCMP(p_enc, "utf8") == 0)
+ validate_utf8 = TRUE;
+
+ if (from_encoding != NULL && STRCMP(from_encoding, "none") == 0)
+ {
+ validate_utf8 = FALSE;
+ MNV_CLEAR(from_encoding);
+ MNV_CLEAR(from_encoding_raw);
+ }
+
+ // Special handling for UTF-16/UCS-2/UTF-32/UCS-4 encodings: convert entire blob before splitting by newlines
+ int from_prop = 0;
+ if (from_encoding != NULL)
+ from_prop = enc_canon_props(from_encoding);
+ if (from_encoding != NULL && (from_prop & (ENC_2BYTE | ENC_4BYTE | ENC_2WORD)))
+ {
+ // Build a temporary buffer from the blob as a whole
+ // Don't use string_from_blob() because it treats NUL as line separator
+ garray_T blob_ga;
+ int nul_size = (from_prop & ENC_4BYTE) ? 4 : 2;
+ ga_init2(&blob_ga, 1, blen + nul_size);
+ for (long i = 0; i < blen; i++)
+ ga_append(&blob_ga, (int)(unsigned char)blob_get(blob, i));
+ // Add NUL terminator (2 bytes for UTF-16/UCS-2, 4 bytes for UTF-32/UCS-4)
+ for (int i = 0; i < nul_size; i++)
+ ga_append(&blob_ga, NUL);
+
+ // Convert the entire blob at once
+ mnvconv_T mnvconv;
+ mnvconv.vc_type = CONV_NONE;
+ // Use raw encoding name for iconv to preserve endianness (utf-16be vs utf-16)
+ // from_encoding_raw is guaranteed non-NULL whenever from_encoding != NULL
+ if (convert_setup_ext(&mnvconv, from_encoding_raw, FALSE, p_enc, FALSE) == FAIL)
+ {
+ ga_clear(&blob_ga);
+ semsg(_(e_str_encoding_from_failed), from_encoding);
+ goto done;
+ }
+ mnvconv.vc_fail = TRUE;
+
+ // Use string_convert_ext with explicit input length
+ string_T converted;
+
+ int len = blen;
+ converted.string =
+ string_convert_ext(&mnvconv, (char_u *)blob_ga.ga_data, &len, NULL);
+ converted.length = len;
+ convert_setup(&mnvconv, NULL, NULL);
+ ga_clear(&blob_ga);
+ append_converted_string_to_list(&converted, validate_utf8, rettv->vval.v_list, from_encoding);
+ }
+ else
+ {
+ // Original logic for non-UTF-16 encodings
+ idx = 0;
+ while (idx < blen)
+ {
+ string_T str;
+
+ if (string_from_blob(blob, &idx, &str) != OK)
+ break;
+
+ if (from_encoding != NULL)
+ {
+ // from_encoding_raw is guaranteed non-NULL whenever from_encoding != NULL
+ int res;
+ string_T converted;
+
+ res = convert_string(&str, from_encoding_raw, p_enc, &converted);
+ mnv_free(str.string);
+ if (res != OK)
+ {
+ semsg(_(e_str_encoding_from_failed), from_encoding);
+ goto done;
+ }
+ str.string = converted.string;
+ str.length = converted.length;
+ }
+
+ if (append_validated_line_to_list(&str, validate_utf8, rettv->vval.v_list) == FAIL)
+ goto done;
+ }
+ }
+
+ // If the blob ends with a newline, we need to add another empty string.
+ if (blen > 0 && blob_get(blob, blen - 1) == NL)
+ list_append_string(rettv->vval.v_list, (char_u *)"", 0);
+
+done:
+ mnv_free(from_encoding);
+ mnv_free(from_encoding_raw);
+}
+
+/*
+ * "str2blob()" function
+ */
+ void
+f_str2blob(typval_T *argvars, typval_T *rettv)
+{
+ blob_T *blob;
+ list_T *list;
+ listitem_T *li;
+
+ if (check_for_list_arg(argvars, 0) == FAIL
+ || check_for_opt_dict_arg(argvars, 1) == FAIL)
+ return;
+
+ if (rettv_blob_alloc(rettv) == FAIL)
+ return;
+
+ blob = rettv->vval.v_blob;
+
+ list = argvars[0].vval.v_list;
+ if (list == NULL)
+ return;
+
+ char_u *to_encoding = NULL;
+ if (argvars[1].v_type != VAR_UNKNOWN)
+ {
+ dict_T *d = argvars[1].vval.v_dict;
+ if (d != NULL)
+ {
+ char_u *enc = dict_get_string(d, "encoding", FALSE);
+ if (enc != NULL)
+ to_encoding = enc_canonize(enc_skip(enc));
+ }
+ }
+
+ FOR_ALL_LIST_ITEMS(list, li)
+ {
+ if (li->li_tv.v_type != VAR_STRING)
+ continue;
+
+ string_T str = {li->li_tv.vval.v_string, 0};
+
+ if (str.string == NULL)
+ {
+ str.string = (char_u *)"";
+ str.length = 0;
+ }
+ else
+ str.length = STRLEN(str.string);
+
+ if (to_encoding != NULL)
+ {
+ int res;
+ string_T converted;
+
+ res = convert_string(&str, p_enc, to_encoding, &converted);
+ if (res != OK)
+ {
+ semsg(_(e_str_encoding_to_failed), to_encoding);
+ goto done;
+ }
+ str.string = converted.string;
+ str.length = converted.length;
+ }
+
+ if (li != list->lv_first)
+ // Each list string item is separated by a newline in the blob
+ ga_append(&blob->bv_ga, NL);
+
+ blob_from_string(str.string, blob);
+
+ if (to_encoding != NULL)
+ mnv_free(str.string);
+ }
+
+done:
+ if (to_encoding != NULL)
+ mnv_free(to_encoding);
+}
+
+/*
+ * "str2list()" function
+ */
+ void
+f_str2list(typval_T *argvars, typval_T *rettv)
+{
+ char_u *p;
+ int utf8 = FALSE;
+
+ if (rettv_list_alloc(rettv) == FAIL)
+ return;
+
+ if (in_mnv9script()
+ && (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_mnv9script()
+ && (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;
+ }
+ mnv_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_mnv9script()
+ && (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_mnv9script()
+ && (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 = mnv_strsave(rettv->vval.v_string);
+}
+
+/*
+ * "strlen()" function
+ */
+ void
+f_strlen(typval_T *argvars, typval_T *rettv)
+{
+ if (in_mnv9script()
+ && 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_mnv9script()
+ && 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_mnv9script()
+ && (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_mnv9script()
+ && (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_mnv9script() && 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_mnv9script()
+ && (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 = mnv_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_mnv9script()
+ && (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 = mnv_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_mnv9script()
+ && (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_mnv9script() && 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 += clen;
+ if (charidx)
+ idx--;
+ }
+
+ rettv->vval.v_number = utf16idx;
+}
+
+/*
+ * "tolower(string)" function
+ */
+ void
+f_tolower(typval_T *argvars, typval_T *rettv)
+{
+ if (in_mnv9script() && 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_mnv9script() && 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_mnv9script()
+ && (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 = mnv_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_mnv9script()
+ && (check_for_string_arg(argvars, 0) == FAIL
+ || check_for_opt_string_arg(argvars, 1) == FAIL
+ || (argvars[1].v_type != VAR_UNKNOWN
+ && check_for_opt_number_arg(argvars, 2) == FAIL)))
+ return;
+
+ head = tv_get_string_buf_chk(&argvars[0], buf1);
+ if (head == NULL)
+ return;
+
+ if (check_for_opt_string_arg(argvars, 1) == FAIL)
+ return;
+
+ if (argvars[1].v_type == VAR_STRING)
+ {
+ mask = tv_get_string_buf_chk(&argvars[1], buf2);
+ if (*mask == NUL)
+ mask = NULL;
+
+ if (argvars[2].v_type != VAR_UNKNOWN)
+ {
+ int error = 0;
+
+ // leading or trailing characters to trim
+ dir = (int)tv_get_number_chk(&argvars[2], &error);
+ if (error)
+ return;
+ if (dir < 0 || dir > 2)
+ {
+ semsg(_(e_invalid_argument_str), tv_get_string(&argvars[2]));
+ return;
+ }
+ }
+ }
+
+ if (dir == 0 || dir == 1)
+ {
+ // Trim leading characters
+ while (*head != NUL)
+ {
+ c1 = PTR2CHAR(head);
+ if (mask == NULL)
+ {
+ if (c1 > ' ' && c1 != 0xa0)
+ break;
+ }
+ else
+ {
+ for (p = mask; *p != NUL; MB_PTR_ADV(p))
+ if (c1 == PTR2CHAR(p))
+ break;
+ if (*p == NUL)
+ break;
+ }
+ MB_PTR_ADV(head);
+ }
+ }
+
+ tail = head + STRLEN(head);
+ if (dir == 0 || dir == 2)
+ {
+ // Trim trailing characters
+ for (; tail > head; tail = prev)
+ {
+ prev = tail;
+ MB_PTR_BACK(head, prev);
+ c1 = PTR2CHAR(prev);
+ if (mask == NULL)
+ {
+ if (c1 > ' ' && c1 != 0xa0)
+ break;
+ }
+ else
+ {
+ for (p = mask; *p != NUL; MB_PTR_ADV(p))
+ if (c1 == PTR2CHAR(p))
+ break;
+ if (*p == NUL)
+ break;
+ }
+ }
+ }
+ rettv->vval.v_string = mnv_strnsave(head, tail - head);
+}
+
+/*
+ * Decodes a URI-encoded string.
+ *
+ * Parameters:
+ * str - The URI-encoded input string (may contain %XX sequences and '+').
+ *
+ * Returns:
+ * A newly allocated string with URI encoding decoded:
+ * - %XX sequences are converted to the corresponding character.
+ * - If the input is malformed (e.g., incomplete % sequence), the original
+ * characters are copied.
+ * The output string will never be longer than the input string.
+ * The caller is responsible for freeing the returned string.
+ *
+ * Returns NULL if input is NULL or memory allocation fails.
+ */
+ static char_u *
+uri_decode(char_u *str)
+{
+ if (str == NULL)
+ return NULL;
+
+ size_t len = STRLEN(str);
+
+ char_u *decoded = alloc(len + 1);
+ if (!decoded)
+ return NULL;
+
+ char_u *p = decoded;
+ size_t i = 0;
+
+ while (i < len)
+ {
+ if (str[i] == '%')
+ {
+ if (i + 2 >= len)
+ {
+ // Malformed encoding
+ *p++ = str[i++];
+ if (str[i] != NUL)
+ *p++ = str[i++];
+ }
+ else
+ {
+ int val = hexhex2nr(&str[i + 1]);
+ if (val != -1)
+ {
+ *p++ = (char_u)val;
+ i += 3;
+ }
+ else
+ {
+ // invalid hex digits following "%"
+ for (int j = 0; j < 3; j++)
+ *p++ = str[i++];
+ }
+ }
+
+ }
+ else
+ *p++ = str[i++];
+ }
+
+ *p = NUL;
+
+ return decoded;
+}
+
+/*
+ * "uri_decode({str})" function
+ */
+ void
+f_uridecode(typval_T *argvars, typval_T *rettv)
+{
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+
+ if (check_for_string_arg(argvars, 0) == FAIL)
+ return;
+
+ rettv->vval.v_string = uri_decode(tv_get_string(&argvars[0]));
+}
+
+/*
+ * Encodes a string for safe use in a URI.
+ *
+ * Parameters:
+ * str - The input string to encode.
+ *
+ * Returns:
+ * A newly allocated string where:
+ * - Alphanumeric characters and '-', '_', '.', '~' are left unchanged.
+ * - All other bytes are encoded as %XX (uppercase hex).
+ * The caller is responsible for freeing the returned string.
+ *
+ * Returns NULL if input is NULL or memory allocation fails.
+ */
+ static char_u *
+uri_encode(char_u *str)
+{
+ if (str == NULL)
+ return NULL;
+
+ size_t len = STRLEN(str);
+
+ // Worst case: every character needs encoding => 3x size + 1 for null
+ // terminator
+ char_u *encoded = alloc(len * 3 + 1);
+ if (encoded == NULL)
+ return NULL;
+
+ char_u *p = encoded;
+
+ for (size_t i = 0; i < len; ++i)
+ {
+ char_u c = str[i];
+ if (ASCII_ISALNUM(c) || c == '-' || c == '_' || c == '.' || c == '~')
+ *p++ = c;
+ else
+ {
+ sprintf((char *)p, "%%%02X", c);
+ p += 3;
+ }
+ }
+
+ *p = NUL;
+
+ return encoded;
+}
+
+/*
+ * "uri_encode({str})" function
+ */
+ void
+f_uriencode(typval_T *argvars, typval_T *rettv)
+{
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+
+ if (check_for_string_arg(argvars, 0) == FAIL)
+ return;
+
+ rettv->vval.v_string = uri_encode(tv_get_string(&argvars[0]));
+}
+
+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 MNV.
+ * 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 mnv_snprintf().
+ *
+ * mnv_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 mnv_vsnprintf() but append to the string.
+ int
+mnv_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 = mnv_vsnprintf(str + len, space, fmt, ap);
+ va_end(ap);
+ return str_l;
+}
+
+ int
+mnv_snprintf(char *str, size_t str_m, const char *fmt, ...)
+{
+ va_list ap;
+ int str_l;
+
+ va_start(ap, fmt);
+ str_l = mnv_vsnprintf(str, str_m, fmt, ap);
+ va_end(ap);
+ return str_l;
+}
+
+/*
+ * Like mnv_snprintf() except the return value can be safely used to increment a
+ * buffer length.
+ * Normal `snprintf()` (and `mnv_snprintf()`) returns the number of bytes that
+ * would have been copied if the destination buffer was large enough.
+ * This means that you cannot rely on it's return value for the destination
+ * length because the destination may be shorter than the source. This function
+ * guarantees the returned length will never be greater than the destination length.
+ */
+ size_t
+mnv_snprintf_safelen(char *str, size_t str_m, const char *fmt, ...)
+{
+ va_list ap;
+ int str_l;
+
+ if (str_m == 0)
+ return 0;
+
+ va_start(ap, fmt);
+ str_l = mnv_vsnprintf_typval(str, str_m, fmt, ap, NULL);
+ va_end(ap);
+
+ if (str_l < 0)
+ {
+ *str = NUL;
+ return 0;
+ }
+ return ((size_t)str_l >= str_m) ? str_m - 1 : (size_t)str_l;
+}
+
+ int
+mnv_vsnprintf(
+ char *str,
+ size_t str_m,
+ const char *fmt,
+ va_list ap)
+{
+ return mnv_vsnprintf_typval(str, str_m, fmt, ap, NULL);
+}
+
+enum
+{
+ TYPE_UNKNOWN = -1,
+ TYPE_INT,
+ TYPE_LONGINT,
+ TYPE_LONGLONGINT,
+ TYPE_UNSIGNEDINT,
+ TYPE_UNSIGNEDLONGINT,
+ TYPE_UNSIGNEDLONGLONGINT,
+ TYPE_POINTER,
+ TYPE_PERCENT,
+ TYPE_CHAR,
+ TYPE_STRING,
+ TYPE_FLOAT
+};
+
+/* Types that can be used in a format string
+ */
+ static int
+format_typeof(
+ const char *type)
+{
+ // allowed values: \0, h, l, L
+ char length_modifier = '\0';
+
+ // current conversion specifier character
+ char fmt_spec = '\0';
+
+ // parse 'h', 'l' and 'll' length modifiers
+ if (*type == 'h' || *type == 'l')
+ {
+ length_modifier = *type;
+ type++;
+ if (length_modifier == 'l' && *type == 'l')
+ {
+ // double l = __int64 / varnumber_T
+ length_modifier = 'L';
+ type++;
+ }
+ }
+ fmt_spec = *type;
+
+ // common synonyms:
+ switch (fmt_spec)
+ {
+ case 'i': fmt_spec = 'd'; break;
+ case '*': fmt_spec = 'd'; length_modifier = 'h'; break;
+ case 'D': fmt_spec = 'd'; length_modifier = 'l'; break;
+ case 'U': fmt_spec = 'u'; length_modifier = 'l'; break;
+ case 'O': fmt_spec = 'o'; length_modifier = 'l'; break;
+ default: break;
+ }
+
+ // get parameter value, do initial processing
+ switch (fmt_spec)
+ {
+ // '%' and 'c' behave similar to 's' regarding flags and field
+ // widths
+ case '%':
+ return TYPE_PERCENT;
+
+ case 'c':
+ return TYPE_CHAR;
+
+ case 's':
+ case 'S':
+ return TYPE_STRING;
+
+ case 'd': case 'u':
+ case 'b': case 'B':
+ case 'o':
+ case 'x': case 'X':
+ case 'p':
+ {
+ // NOTE: the u, b, o, x, X and p conversion specifiers
+ // imply the value is unsigned; d implies a signed
+ // value
+
+ // 0 if numeric argument is zero (or if pointer is
+ // NULL for 'p'), +1 if greater than zero (or nonzero
+ // for unsigned arguments), -1 if negative (unsigned
+ // argument is never negative)
+
+ if (fmt_spec == 'p')
+ return TYPE_POINTER;
+ else if (fmt_spec == 'b' || fmt_spec == 'B')
+ return TYPE_UNSIGNEDLONGLONGINT;
+ else if (fmt_spec == 'd')
+ {
+ // signed
+ switch (length_modifier)
+ {
+ case '\0':
+ case 'h':
+ // char and short arguments are passed as int.
+ return TYPE_INT;
+ case 'l':
+ return TYPE_LONGINT;
+ case 'L':
+ return TYPE_LONGLONGINT;
+ }
+ }
+ else
+ {
+ // unsigned
+ switch (length_modifier)
+ {
+ case '\0':
+ case 'h':
+ return TYPE_UNSIGNEDINT;
+ case 'l':
+ return TYPE_UNSIGNEDLONGINT;
+ case 'L':
+ return TYPE_UNSIGNEDLONGLONGINT;
+ }
+ }
+ }
+ break;
+
+ case 'f':
+ case 'F':
+ case 'e':
+ case 'E':
+ case 'g':
+ case 'G':
+ return TYPE_FLOAT;
+ }
+
+ return TYPE_UNKNOWN;
+}
+
+ static char *
+format_typename(
+ const char *type)
+{
+ switch (format_typeof(type))
+ {
+ case TYPE_INT:
+ return _(typename_int);
+
+ case TYPE_LONGINT:
+ return _(typename_longint);
+
+ case TYPE_LONGLONGINT:
+ return _(typename_longlongint);
+
+ case TYPE_UNSIGNEDINT:
+ return _(typename_unsignedint);
+
+ case TYPE_UNSIGNEDLONGINT:
+ return _(typename_unsignedlongint);
+
+ case TYPE_UNSIGNEDLONGLONGINT:
+ return _(typename_unsignedlonglongint);
+
+ case TYPE_POINTER:
+ return _(typename_pointer);
+
+ case TYPE_PERCENT:
+ return _(typename_percent);
+
+ case TYPE_CHAR:
+ return _(typename_char);
+
+ case TYPE_STRING:
+ return _(typename_string);
+
+ case TYPE_FLOAT:
+ return _(typename_float);
+ }
+
+ return _(typename_unknown);
+}
+
+ static int
+adjust_types(
+ const char ***ap_types,
+ int arg,
+ int *num_posarg,
+ const char *type)
+{
+ if (arg <= 0)
+ {
+ semsg(_( e_invalid_format_specifier_str), type);
+ return FAIL;
+ }
+
+ if (*ap_types == NULL || *num_posarg < arg)
+ {
+ int idx;
+ const char **new_types;
+
+ if (*ap_types == NULL)
+ new_types = ALLOC_CLEAR_MULT(const char *, arg);
+ else
+ new_types = mnv_realloc((char **)*ap_types,
+ arg * sizeof(const char *));
+
+ if (new_types == NULL)
+ return FAIL;
+
+ for (idx = *num_posarg; idx < arg; ++idx)
+ new_types[idx] = NULL;
+
+ *ap_types = new_types;
+ *num_posarg = arg;
+ }
+
+ if ((*ap_types)[arg - 1] != NULL)
+ {
+ if ((*ap_types)[arg - 1][0] == '*' || type[0] == '*')
+ {
+ const char *pt = type;
+ if (pt[0] == '*')
+ pt = (*ap_types)[arg - 1];
+
+ if (pt[0] != '*')
+ {
+ switch (pt[0])
+ {
+ case 'd':
+ case 'i': break;
+ default:
+ semsg(_(e_positional_num_field_spec_reused_str_str), arg, format_typename((*ap_types)[arg - 1]), format_typename(type));
+ return FAIL;
+ }
+ }
+ }
+ else
+ {
+ if (format_typeof(type) != format_typeof((*ap_types)[arg - 1]))
+ {
+ semsg(_( e_positional_arg_num_type_inconsistent_str_str), arg, format_typename(type), format_typename((*ap_types)[arg - 1]));
+ return FAIL;
+ }
+ }
+ }
+
+ (*ap_types)[arg - 1] = type;
+
+ return OK;
+}
+
+ static void
+format_overflow_error(const char *pstart)
+{
+ size_t arglen = 0;
+ char *argcopy = NULL;
+ const char *p = pstart;
+
+ while (MNV_ISDIGIT((int)(*p)))
+ ++p;
+
+ arglen = p - pstart;
+ argcopy = ALLOC_CLEAR_MULT(char, arglen + 1);
+ if (argcopy != NULL)
+ {
+ strncpy(argcopy, pstart, arglen);
+ semsg(_( e_val_too_large), argcopy);
+ free(argcopy);
+ }
+ else
+ semsg(_(e_out_of_memory_allocating_nr_bytes), arglen);
+}
+
+# define MAX_ALLOWED_STRING_WIDTH 1048576 // 1 MiB
+
+ static int
+get_unsigned_int(
+ const char *pstart,
+ const char **p,
+ unsigned int *uj,
+ int overflow_err)
+{
+ *uj = **p - '0';
+ ++*p;
+
+ while (MNV_ISDIGIT((int)(**p)) && *uj < MAX_ALLOWED_STRING_WIDTH)
+ {
+ *uj = 10 * *uj + (unsigned int)(**p - '0');
+ ++*p;
+ }
+
+ if (*uj > MAX_ALLOWED_STRING_WIDTH)
+ {
+ if (overflow_err)
+ {
+ format_overflow_error(pstart);
+ return FAIL;
+ }
+ else
+ *uj = MAX_ALLOWED_STRING_WIDTH;
+ }
+
+ return OK;
+}
+
+
+ static int
+parse_fmt_types(
+ const char ***ap_types,
+ int *num_posarg,
+ const char *fmt,
+ typval_T *tvs UNUSED
+ )
+{
+ const char *p = fmt;
+ const char *arg = NULL;
+
+ int any_pos = 0;
+ int any_arg = 0;
+ int arg_idx;
+
+# define CHECK_POS_ARG do { \
+ if (any_pos && any_arg) \
+ { \
+ semsg(_( e_cannot_mix_positional_and_non_positional_str), fmt); \
+ goto error; \
+ } \
+} while (0);
+
+ if (p == NULL)
+ return OK;
+
+ while (*p != NUL)
+ {
+ if (*p != '%')
+ {
+ const char *q = strchr(p + 1, '%');
+ size_t n = (q == NULL) ? STRLEN(p) : (size_t)(q - p);
+
+ p += n;
+ }
+ else
+ {
+ // allowed values: \0, h, l, L
+ char length_modifier = '\0';
+
+ // variable for positional arg
+ int pos_arg = -1;
+ const char *ptype = NULL;
+ const char *pstart = p+1;
+
+ p++; // skip '%'
+
+ // First check to see if we find a positional
+ // argument specifier
+ ptype = p;
+
+ while (MNV_ISDIGIT(*ptype))
+ ++ptype;
+
+ if (*ptype == '$')
+ {
+ if (*p == '0')
+ {
+ // 0 flag at the wrong place
+ semsg(_( e_invalid_format_specifier_str), fmt);
+ goto error;
+ }
+
+ // Positional argument
+ unsigned int uj;
+
+ if (get_unsigned_int(pstart, &p, &uj, tvs != NULL) == FAIL)
+ goto error;
+
+ pos_arg = uj;
+
+ any_pos = 1;
+ CHECK_POS_ARG;
+
+ ++p;
+ }
+
+ // parse flags
+ while (*p == '0' || *p == '-' || *p == '+' || *p == ' '
+ || *p == '#' || *p == '\'')
+ {
+ switch (*p)
+ {
+ case '0': break;
+ case '-': break;
+ case '+': break;
+ case ' ': // If both the ' ' and '+' flags appear, the ' '
+ // flag should be ignored
+ break;
+ case '#': break;
+ case '\'': break;
+ }
+ p++;
+ }
+ // If the '0' and '-' flags both appear, the '0' flag should be
+ // ignored.
+
+ // parse field width
+ if (*(arg = p) == '*')
+ {
+ p++;
+
+ if (MNV_ISDIGIT((int)(*p)))
+ {
+ // Positional argument field width
+ unsigned int uj;
+
+ if (get_unsigned_int(arg + 1, &p, &uj, tvs != NULL) == FAIL)
+ goto error;
+
+ if (*p != '$')
+ {
+ semsg(_( e_invalid_format_specifier_str), fmt);
+ goto error;
+ }
+ else
+ {
+ ++p;
+ any_pos = 1;
+ CHECK_POS_ARG;
+
+ if (adjust_types(ap_types, uj, num_posarg, arg) == FAIL)
+ goto error;
+ }
+ }
+ else
+ {
+ any_arg = 1;
+ CHECK_POS_ARG;
+ }
+ }
+ else if (MNV_ISDIGIT((int)(*p)))
+ {
+ // size_t could be wider than unsigned int; make sure we treat
+ // argument like common implementations do
+ const char *digstart = p;
+ unsigned int uj;
+
+ if (get_unsigned_int(digstart, &p, &uj, tvs != NULL) == FAIL)
+ goto error;
+
+ if (*p == '$')
+ {
+ semsg(_( e_invalid_format_specifier_str), fmt);
+ goto error;
+ }
+ }
+
+ // parse precision
+ if (*p == '.')
+ {
+ p++;
+
+ if (*(arg = p) == '*')
+ {
+ p++;
+
+ if (MNV_ISDIGIT((int)(*p)))
+ {
+ // Parse precision
+ unsigned int uj;
+
+ if (get_unsigned_int(arg + 1, &p, &uj, tvs != NULL) == FAIL)
+ goto error;
+
+ if (*p == '$')
+ {
+ any_pos = 1;
+ CHECK_POS_ARG;
+
+ ++p;
+
+ if (adjust_types(ap_types, uj, num_posarg, arg) == FAIL)
+ goto error;
+ }
+ else
+ {
+ semsg(_( e_invalid_format_specifier_str), fmt);
+ goto error;
+ }
+ }
+ else
+ {
+ any_arg = 1;
+ CHECK_POS_ARG;
+ }
+ }
+ else if (MNV_ISDIGIT((int)(*p)))
+ {
+ // size_t could be wider than unsigned int; make sure we
+ // treat argument like common implementations do
+ const char *digstart = p;
+ unsigned int uj;
+
+ if (get_unsigned_int(digstart, &p, &uj, tvs != NULL) == FAIL)
+ goto error;
+
+ if (*p == '$')
+ {
+ semsg(_( e_invalid_format_specifier_str), fmt);
+ goto error;
+ }
+ }
+ }
+
+ if (pos_arg != -1)
+ {
+ any_pos = 1;
+ CHECK_POS_ARG;
+
+ ptype = p;
+ }
+
+ // parse 'h', 'l' and 'll' length modifiers
+ if (*p == 'h' || *p == 'l')
+ {
+ length_modifier = *p;
+ p++;
+ if (length_modifier == 'l' && *p == 'l')
+ {
+ // double l = __int64 / varnumber_T
+ // length_modifier = 'L';
+ p++;
+ }
+ }
+
+ switch (*p)
+ {
+ // Check for known format specifiers. % is special!
+ case 'i':
+ case '*':
+ case 'd':
+ case 'u':
+ case 'o':
+ case 'D':
+ case 'U':
+ case 'O':
+ case 'x':
+ case 'X':
+ case 'b':
+ case 'B':
+ case 'c':
+ case 's':
+ case 'S':
+ case 'p':
+ case 'f':
+ case 'F':
+ case 'e':
+ case 'E':
+ case 'g':
+ case 'G':
+ if (pos_arg != -1)
+ {
+ if (adjust_types(ap_types, pos_arg, num_posarg, ptype) == FAIL)
+ goto error;
+ }
+ else
+ {
+ any_arg = 1;
+ CHECK_POS_ARG;
+ }
+ break;
+
+ default:
+ if (pos_arg != -1)
+ {
+ semsg(_( e_cannot_mix_positional_and_non_positional_str), fmt);
+ goto error;
+ }
+ }
+
+ if (*p != NUL)
+ p++; // step over the just processed conversion specifier
+ }
+ }
+
+ for (arg_idx = 0; arg_idx < *num_posarg; ++arg_idx)
+ {
+ if ((*ap_types)[arg_idx] == NULL)
+ {
+ semsg(_(e_fmt_arg_nr_unused_str), arg_idx + 1, fmt);
+ goto error;
+ }
+
+# if defined(FEAT_EVAL)
+ if (tvs != NULL && tvs[arg_idx].v_type == VAR_UNKNOWN)
+ {
+ semsg(_(e_positional_nr_out_of_bounds_str), arg_idx + 1, fmt);
+ goto error;
+ }
+# endif
+ }
+
+ return OK;
+
+error:
+ mnv_free((char**)*ap_types);
+ *ap_types = NULL;
+ *num_posarg = 0;
+ return FAIL;
+}
+
+ static void
+skip_to_arg(
+ const char **ap_types,
+ va_list ap_start,
+ va_list *ap,
+ int *arg_idx,
+ int *arg_cur,
+ const char *fmt)
+{
+ int arg_min = 0;
+
+ if (*arg_cur + 1 == *arg_idx)
+ {
+ ++*arg_cur;
+ ++*arg_idx;
+ return;
+ }
+
+ if (*arg_cur >= *arg_idx)
+ {
+ // Reset ap to ap_start and skip arg_idx - 1 types
+ va_end(*ap);
+ va_copy(*ap, ap_start);
+ }
+ else
+ {
+ // Skip over any we should skip
+ arg_min = *arg_cur;
+ }
+
+ for (*arg_cur = arg_min; *arg_cur < *arg_idx - 1; ++*arg_cur)
+ {
+ const char *p;
+
+ if (ap_types == NULL || ap_types[*arg_cur] == NULL)
+ {
+ siemsg(e_aptypes_is_null_nr_str, *arg_cur, fmt);
+ return;
+ }
+
+ p = ap_types[*arg_cur];
+
+ int fmt_type = format_typeof(p);
+
+ // get parameter value, do initial processing
+ switch (fmt_type)
+ {
+ case TYPE_PERCENT:
+ case TYPE_UNKNOWN:
+ break;
+
+ case TYPE_CHAR:
+ va_arg(*ap, int);
+ break;
+
+ case TYPE_STRING:
+ va_arg(*ap, char *);
+ break;
+
+ case TYPE_POINTER:
+ va_arg(*ap, void *);
+ break;
+
+ case TYPE_INT:
+ va_arg(*ap, int);
+ break;
+
+ case TYPE_LONGINT:
+ va_arg(*ap, long int);
+ break;
+
+ case TYPE_LONGLONGINT:
+ va_arg(*ap, varnumber_T);
+ break;
+
+ case TYPE_UNSIGNEDINT:
+ va_arg(*ap, unsigned int);
+ break;
+
+ case TYPE_UNSIGNEDLONGINT:
+ va_arg(*ap, unsigned long int);
+ break;
+
+ case TYPE_UNSIGNEDLONGLONGINT:
+ va_arg(*ap, uvarnumber_T);
+ break;
+
+ case TYPE_FLOAT:
+ va_arg(*ap, double);
+ break;
+ }
+ }
+
+ // Because we know that after we return from this call,
+ // a va_arg() call is made, we can pre-emptively
+ // increment the current argument index.
+ ++*arg_cur;
+ ++*arg_idx;
+
+ return;
+}
+
+ int
+mnv_vsnprintf_typval(
+ char *str,
+ size_t str_m,
+ const char *fmt,
+ va_list ap_start,
+ typval_T *tvs)
+{
+ size_t str_l = 0;
+ const char *p = fmt;
+ int arg_cur = 0;
+ int num_posarg = 0;
+ int arg_idx = 1;
+ va_list ap;
+ const char **ap_types = NULL;
+
+ if (parse_fmt_types(&ap_types, &num_posarg, fmt, tvs) == FAIL)
+ return 0;
+
+ va_copy(ap, ap_start);
+
+ if (p == NULL)
+ p = "";
+ while (*p != NUL)
+ {
+ if (*p != '%')
+ {
+ const char *q = strchr(p + 1, '%');
+ size_t n = (q == NULL) ? STRLEN(p) : (size_t)(q - p);
+
+ // Copy up to the next '%' or NUL without any changes.
+ if (str_l < str_m)
+ {
+ size_t avail = str_m - str_l;
+
+ mch_memmove(str + str_l, p, n > avail ? avail : n);
+ }
+ p += n;
+ str_l += n;
+ }
+ else
+ {
+ size_t min_field_width = 0, precision = 0;
+ int zero_padding = 0, precision_specified = 0, justify_left = 0;
+ int alternate_form = 0, force_sign = 0;
+
+ // If both the ' ' and '+' flags appear, the ' ' flag should be
+ // ignored.
+ int space_for_positive = 1;
+
+ // allowed values: \0, h, l, L
+ char length_modifier = '\0';
+
+ // temporary buffer for simple numeric->string conversion
+# define TMP_LEN 350 // On my system 1e308 is the biggest number possible.
+ // That sounds reasonable to use as the maximum
+ // printable.
+ char tmp[TMP_LEN];
+
+ // string address in case of string argument
+ const char *str_arg = NULL;
+
+ // natural field width of arg without padding and sign
+ size_t str_arg_l;
+
+ // unsigned char argument value - only defined for c conversion.
+ // N.B. standard explicitly states the char argument for the c
+ // conversion is unsigned
+ unsigned char uchar_arg;
+
+ // number of zeros to be inserted for numeric conversions as
+ // required by the precision or minimal field width
+ size_t number_of_zeros_to_pad = 0;
+
+ // index into tmp where zero padding is to be inserted
+ size_t zero_padding_insertion_ind = 0;
+
+ // current conversion specifier character
+ char fmt_spec = '\0';
+
+ // buffer for 's' and 'S' specs
+ char_u *tofree = NULL;
+
+ // variables for positional arg
+ int pos_arg = -1;
+ const char *ptype;
+
+
+ p++; // skip '%'
+
+ // First check to see if we find a positional
+ // argument specifier
+ ptype = p;
+
+ while (MNV_ISDIGIT(*ptype))
+ ++ptype;
+
+ if (*ptype == '$')
+ {
+ // Positional argument
+ const char *digstart = p;
+ unsigned int uj;
+
+ if (get_unsigned_int(digstart, &p, &uj, tvs != NULL) == FAIL)
+ goto error;
+
+ pos_arg = uj;
+
+ ++p;
+ }
+
+ // parse flags
+ while (*p == '0' || *p == '-' || *p == '+' || *p == ' '
+ || *p == '#' || *p == '\'')
+ {
+ switch (*p)
+ {
+ case '0': zero_padding = 1; break;
+ case '-': justify_left = 1; break;
+ case '+': force_sign = 1; space_for_positive = 0; break;
+ case ' ': force_sign = 1;
+ // If both the ' ' and '+' flags appear, the ' '
+ // flag should be ignored
+ break;
+ case '#': alternate_form = 1; break;
+ case '\'': break;
+ }
+ p++;
+ }
+ // If the '0' and '-' flags both appear, the '0' flag should be
+ // ignored.
+
+ // parse field width
+ if (*p == '*')
+ {
+ int j;
+ const char *digstart = p + 1;
+
+ p++;
+
+ if (MNV_ISDIGIT((int)(*p)))
+ {
+ // Positional argument field width
+ unsigned int uj;
+
+ if (get_unsigned_int(digstart, &p, &uj, tvs != NULL) == FAIL)
+ goto error;
+
+ arg_idx = uj;
+
+ ++p;
+ }
+
+ j =
+# if defined(FEAT_EVAL)
+ tvs != NULL ? tv_nr(tvs, &arg_idx) :
+# endif
+ (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
+ &arg_cur, fmt),
+ va_arg(ap, int));
+
+ if (j > MAX_ALLOWED_STRING_WIDTH)
+ {
+ if (tvs != NULL)
+ {
+ format_overflow_error(digstart);
+ goto error;
+ }
+ else
+ j = MAX_ALLOWED_STRING_WIDTH;
+ }
+
+ if (j >= 0)
+ min_field_width = j;
+ else
+ {
+ min_field_width = -j;
+ justify_left = 1;
+ }
+ }
+ else if (MNV_ISDIGIT((int)(*p)))
+ {
+ // size_t could be wider than unsigned int; make sure we treat
+ // argument like common implementations do
+ const char *digstart = p;
+ unsigned int uj;
+
+ if (get_unsigned_int(digstart, &p, &uj, tvs != NULL) == FAIL)
+ goto error;
+
+ min_field_width = uj;
+ }
+
+ // parse precision
+ if (*p == '.')
+ {
+ p++;
+ precision_specified = 1;
+
+ if (MNV_ISDIGIT((int)(*p)))
+ {
+ // size_t could be wider than unsigned int; make sure we
+ // treat argument like common implementations do
+ const char *digstart = p;
+ unsigned int uj;
+
+ if (get_unsigned_int(digstart, &p, &uj, tvs != NULL) == FAIL)
+ goto error;
+
+ precision = uj;
+ }
+ else if (*p == '*')
+ {
+ int j;
+ const char *digstart = p;
+
+ p++;
+
+ if (MNV_ISDIGIT((int)(*p)))
+ {
+ // positional argument
+ unsigned int uj;
+
+ if (get_unsigned_int(digstart, &p, &uj, tvs != NULL) == FAIL)
+ goto error;
+
+ arg_idx = uj;
+
+ ++p;
+ }
+
+ j =
+# if defined(FEAT_EVAL)
+ tvs != NULL ? tv_nr(tvs, &arg_idx) :
+# endif
+ (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
+ &arg_cur, fmt),
+ va_arg(ap, int));
+
+ if (j > MAX_ALLOWED_STRING_WIDTH)
+ {
+ if (tvs != NULL)
+ {
+ format_overflow_error(digstart);
+ goto error;
+ }
+ else
+ j = MAX_ALLOWED_STRING_WIDTH;
+ }
+
+ if (j >= 0)
+ precision = j;
+ else
+ {
+ precision_specified = 0;
+ precision = 0;
+ }
+ }
+ }
+
+ // parse 'h', 'l' and 'll' length modifiers
+ if (*p == 'h' || *p == 'l')
+ {
+ length_modifier = *p;
+ p++;
+ if (length_modifier == 'l' && *p == 'l')
+ {
+ // double l = __int64 / varnumber_T
+ length_modifier = 'L';
+ p++;
+ }
+ }
+ fmt_spec = *p;
+
+ // common synonyms:
+ switch (fmt_spec)
+ {
+ case 'i': fmt_spec = 'd'; break;
+ case 'D': fmt_spec = 'd'; length_modifier = 'l'; break;
+ case 'U': fmt_spec = 'u'; length_modifier = 'l'; break;
+ case 'O': fmt_spec = 'o'; length_modifier = 'l'; break;
+ default: break;
+ }
+
+# if defined(FEAT_EVAL)
+ switch (fmt_spec)
+ {
+ case 'd': case 'u': case 'o': case 'x': case 'X':
+ if (tvs != NULL && length_modifier == '\0')
+ length_modifier = 'L';
+ }
+# endif
+
+ if (pos_arg != -1)
+ arg_idx = pos_arg;
+
+ // get parameter value, do initial processing
+ switch (fmt_spec)
+ {
+ // '%' and 'c' behave similar to 's' regarding flags and field
+ // widths
+ case '%':
+ case 'c':
+ case 's':
+ case 'S':
+ str_arg_l = 1;
+ switch (fmt_spec)
+ {
+ case '%':
+ str_arg = p;
+ break;
+
+ case 'c':
+ {
+ int j;
+
+ j =
+# if defined(FEAT_EVAL)
+ tvs != NULL ? tv_nr(tvs, &arg_idx) :
+# endif
+ (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
+ &arg_cur, fmt),
+ va_arg(ap, int));
+
+ // standard demands unsigned char
+ uchar_arg = (unsigned char)j;
+ str_arg = (char *)&uchar_arg;
+ break;
+ }
+
+ case 's':
+ case 'S':
+ str_arg =
+# if defined(FEAT_EVAL)
+ tvs != NULL ? tv_str(tvs, &arg_idx, &tofree) :
+# endif
+ (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
+ &arg_cur, fmt),
+ va_arg(ap, char *));
+
+ if (str_arg == NULL)
+ {
+ str_arg = "[NULL]";
+ str_arg_l = 6;
+ }
+ // make sure not to address string beyond the specified
+ // precision !!!
+ else if (!precision_specified)
+ str_arg_l = strlen(str_arg);
+ // truncate string if necessary as requested by precision
+ else if (precision == 0)
+ str_arg_l = 0;
+ else
+ {
+ // memchr on HP does not like n > 2^31 !!!
+ const char *q = memchr(str_arg, '\0',
+ precision <= (size_t)0x7fffffffL ? precision
+ : (size_t)0x7fffffffL);
+
+ str_arg_l = (q == NULL) ? precision
+ : (size_t)(q - str_arg);
+ }
+ if (fmt_spec == 'S')
+ {
+ char_u *p1;
+ size_t i;
+ int cell;
+
+ for (i = 0, p1 = (char_u *)str_arg; *p1;
+ p1 += mb_ptr2len(p1))
+ {
+ cell = mb_ptr2cells(p1);
+ if (precision_specified && i + cell > precision)
+ break;
+ i += cell;
+ }
+
+ str_arg_l = p1 - (char_u *)str_arg;
+ if (min_field_width != 0)
+ min_field_width += str_arg_l - i;
+ }
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ case 'd': case 'u':
+ case 'b': case 'B':
+ case 'o':
+ case 'x': case 'X':
+ case 'p':
+ {
+ // NOTE: the u, b, o, x, X and p conversion specifiers
+ // imply the value is unsigned; d implies a signed
+ // value
+
+ // 0 if numeric argument is zero (or if pointer is
+ // NULL for 'p'), +1 if greater than zero (or nonzero
+ // for unsigned arguments), -1 if negative (unsigned
+ // argument is never negative)
+ int arg_sign = 0;
+
+ // only set for length modifier h, or for no length
+ // modifiers
+ int int_arg = 0;
+ unsigned int uint_arg = 0;
+
+ // only set for length modifier l
+ long int long_arg = 0;
+ unsigned long int ulong_arg = 0;
+
+ // only set for length modifier ll
+ varnumber_T llong_arg = 0;
+ uvarnumber_T ullong_arg = 0;
+
+ // only set for b conversion
+ uvarnumber_T bin_arg = 0;
+
+ // pointer argument value -only defined for p
+ // conversion
+ void *ptr_arg = NULL;
+
+ if (fmt_spec == 'p')
+ {
+ length_modifier = '\0';
+ ptr_arg =
+# if defined(FEAT_EVAL)
+ tvs != NULL ? (void *)tv_str(tvs, &arg_idx,
+ NULL) :
+# endif
+ (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
+ &arg_cur, fmt),
+ va_arg(ap, void *));
+
+ if (ptr_arg != NULL)
+ arg_sign = 1;
+ }
+ else if (fmt_spec == 'b' || fmt_spec == 'B')
+ {
+ bin_arg =
+# if defined(FEAT_EVAL)
+ tvs != NULL ?
+ (uvarnumber_T)tv_nr(tvs, &arg_idx) :
+# endif
+ (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
+ &arg_cur, fmt),
+ va_arg(ap, uvarnumber_T));
+
+ if (bin_arg != 0)
+ arg_sign = 1;
+ }
+ else if (fmt_spec == 'd')
+ {
+ // signed
+ switch (length_modifier)
+ {
+ case '\0':
+ case 'h':
+ // char and short arguments are passed as int.
+ int_arg =
+# if defined(FEAT_EVAL)
+ tvs != NULL ? tv_nr(tvs, &arg_idx) :
+# endif
+ (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
+ &arg_cur, fmt),
+ va_arg(ap, int));
+
+ if (int_arg > 0)
+ arg_sign = 1;
+ else if (int_arg < 0)
+ arg_sign = -1;
+ break;
+ case 'l':
+ long_arg =
+# if defined(FEAT_EVAL)
+ tvs != NULL ? tv_nr(tvs, &arg_idx) :
+# endif
+ (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
+ &arg_cur, fmt),
+ va_arg(ap, long int));
+
+ if (long_arg > 0)
+ arg_sign = 1;
+ else if (long_arg < 0)
+ arg_sign = -1;
+ break;
+ case 'L':
+ llong_arg =
+# if defined(FEAT_EVAL)
+ tvs != NULL ? tv_nr(tvs, &arg_idx) :
+# endif
+ (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
+ &arg_cur, fmt),
+ va_arg(ap, varnumber_T));
+
+ if (llong_arg > 0)
+ arg_sign = 1;
+ else if (llong_arg < 0)
+ arg_sign = -1;
+ break;
+ }
+ }
+ else
+ {
+ // unsigned
+ switch (length_modifier)
+ {
+ case '\0':
+ case 'h':
+ uint_arg =
+# if defined(FEAT_EVAL)
+ tvs != NULL ? (unsigned)
+ tv_nr(tvs, &arg_idx) :
+# endif
+ (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
+ &arg_cur, fmt),
+ va_arg(ap, unsigned int));
+
+ if (uint_arg != 0)
+ arg_sign = 1;
+ break;
+ case 'l':
+ ulong_arg =
+# if defined(FEAT_EVAL)
+ tvs != NULL ? (unsigned long)
+ tv_nr(tvs, &arg_idx) :
+# endif
+ (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
+ &arg_cur, fmt),
+ va_arg(ap, unsigned long int));
+
+ if (ulong_arg != 0)
+ arg_sign = 1;
+ break;
+ case 'L':
+ ullong_arg =
+# if defined(FEAT_EVAL)
+ tvs != NULL ? (uvarnumber_T)
+ tv_nr(tvs, &arg_idx) :
+# endif
+ (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
+ &arg_cur, fmt),
+ va_arg(ap, uvarnumber_T));
+
+ if (ullong_arg != 0)
+ arg_sign = 1;
+ break;
+ }
+ }
+
+ str_arg = tmp;
+ str_arg_l = 0;
+
+ // NOTE:
+ // For d, i, u, o, x, and X conversions, if precision is
+ // specified, the '0' flag should be ignored. This is so
+ // with Solaris 2.6, Digital UNIX 4.0, HPUX 10, Linux,
+ // FreeBSD, NetBSD; but not with Perl.
+ if (precision_specified)
+ zero_padding = 0;
+ if (fmt_spec == 'd')
+ {
+ if (force_sign && arg_sign >= 0)
+ tmp[str_arg_l++] = space_for_positive ? ' ' : '+';
+ // leave negative numbers for sprintf to handle, to
+ // avoid handling tricky cases like (short int)-32768
+ }
+ else if (alternate_form)
+ {
+ if (arg_sign != 0
+ && (fmt_spec == 'b' || fmt_spec == 'B'
+ || fmt_spec == 'x' || fmt_spec == 'X') )
+ {
+ tmp[str_arg_l++] = '0';
+ tmp[str_arg_l++] = fmt_spec;
+ }
+ // alternate form should have no effect for p
+ // conversion, but ...
+ }
+
+ zero_padding_insertion_ind = str_arg_l;
+ if (!precision_specified)
+ precision = 1; // default precision is 1
+ if (precision == 0 && arg_sign == 0)
+ {
+ // When zero value is formatted with an explicit
+ // precision 0, the resulting formatted string is
+ // empty (d, i, u, b, B, o, x, X, p).
+ }
+ else
+ {
+ char f[6];
+ int f_l = 0;
+
+ // construct a simple format string for sprintf
+ f[f_l++] = '%';
+ if (!length_modifier)
+ ;
+ else if (length_modifier == 'L')
+ {
+# ifdef MSWIN
+ f[f_l++] = 'I';
+ f[f_l++] = '6';
+ f[f_l++] = '4';
+# else
+ f[f_l++] = 'l';
+ f[f_l++] = 'l';
+# endif
+ }
+ else
+ f[f_l++] = length_modifier;
+ f[f_l++] = fmt_spec;
+ f[f_l++] = '\0';
+
+ if (fmt_spec == 'p')
+ str_arg_l += sprintf(tmp + str_arg_l, f, ptr_arg);
+ else if (fmt_spec == 'b' || fmt_spec == 'B')
+ {
+ char b[8 * sizeof(uvarnumber_T)];
+ size_t b_l = 0;
+ uvarnumber_T bn = bin_arg;
+
+ do
+ {
+ b[sizeof(b) - ++b_l] = '0' + (bn & 0x1);
+ bn >>= 1;
+ }
+ while (bn != 0);
+
+ memcpy(tmp + str_arg_l, b + sizeof(b) - b_l, b_l);
+ str_arg_l += b_l;
+ }
+ else if (fmt_spec == 'd')
+ {
+ // signed
+ switch (length_modifier)
+ {
+ case '\0': str_arg_l += sprintf(
+ tmp + str_arg_l, f,
+ int_arg);
+ break;
+ case 'h': str_arg_l += sprintf(
+ tmp + str_arg_l, f,
+ (short)int_arg);
+ break;
+ case 'l': str_arg_l += sprintf(
+ tmp + str_arg_l, f, long_arg);
+ break;
+ case 'L': str_arg_l += sprintf(
+ tmp + str_arg_l, f, llong_arg);
+ break;
+ }
+ }
+ else
+ {
+ // unsigned
+ switch (length_modifier)
+ {
+ case '\0': str_arg_l += sprintf(
+ tmp + str_arg_l, f,
+ uint_arg);
+ break;
+ case 'h': str_arg_l += sprintf(
+ tmp + str_arg_l, f,
+ (unsigned short)uint_arg);
+ break;
+ case 'l': str_arg_l += sprintf(
+ tmp + str_arg_l, f, ulong_arg);
+ break;
+ case 'L': str_arg_l += sprintf(
+ tmp + str_arg_l, f, ullong_arg);
+ break;
+ }
+ }
+
+ // include the optional minus sign and possible
+ // "0x" in the region before the zero padding
+ // insertion point
+ if (zero_padding_insertion_ind < str_arg_l
+ && tmp[zero_padding_insertion_ind] == '-')
+ zero_padding_insertion_ind++;
+ if (zero_padding_insertion_ind + 1 < str_arg_l
+ && tmp[zero_padding_insertion_ind] == '0'
+ && (tmp[zero_padding_insertion_ind + 1] == 'x'
+ || tmp[zero_padding_insertion_ind + 1] == 'X'))
+ zero_padding_insertion_ind += 2;
+ }
+
+ {
+ size_t num_of_digits = str_arg_l
+ - zero_padding_insertion_ind;
+
+ if (alternate_form && fmt_spec == 'o'
+ // unless zero is already the first
+ // character
+ && !(zero_padding_insertion_ind < str_arg_l
+ && tmp[zero_padding_insertion_ind] == '0'))
+ {
+ // assure leading zero for alternate-form
+ // octal numbers
+ if (!precision_specified
+ || precision < num_of_digits + 1)
+ {
+ // precision is increased to force the
+ // first character to be zero, except if a
+ // zero value is formatted with an
+ // explicit precision of zero
+ precision = num_of_digits + 1;
+ }
+ }
+ // zero padding to specified precision?
+ if (num_of_digits < precision)
+ number_of_zeros_to_pad = precision - num_of_digits;
+ }
+ // zero padding to specified minimal field width?
+ if (!justify_left && zero_padding)
+ {
+ int n = (int)(min_field_width - (str_arg_l
+ + number_of_zeros_to_pad));
+ if (n > 0)
+ number_of_zeros_to_pad += n;
+ }
+ break;
+ }
+
+ case 'f':
+ case 'F':
+ case 'e':
+ case 'E':
+ case 'g':
+ case 'G':
+ {
+ // Floating point.
+ double f;
+ double abs_f;
+ char format[40];
+ int l;
+ int remove_trailing_zeroes = FALSE;
+
+ f =
+# if defined(FEAT_EVAL)
+ tvs != NULL ? tv_float(tvs, &arg_idx) :
+# endif
+ (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
+ &arg_cur, fmt),
+ va_arg(ap, double));
+
+ abs_f = f < 0 ? -f : f;
+
+ if (fmt_spec == 'g' || fmt_spec == 'G')
+ {
+ // Would be nice to use %g directly, but it prints
+ // "1.0" as "1", we don't want that.
+ if ((abs_f >= 0.001 && abs_f < 10000000.0)
+ || abs_f == 0.0)
+ fmt_spec = ASCII_ISUPPER(fmt_spec) ? 'F' : 'f';
+ else
+ fmt_spec = fmt_spec == 'g' ? 'e' : 'E';
+ remove_trailing_zeroes = TRUE;
+ }
+
+ if ((fmt_spec == 'f' || fmt_spec == 'F') &&
+# ifdef VAX
+ abs_f > 1.0e38
+# else
+ abs_f > 1.0e307
+# endif
+ )
+ {
+ // Avoid a buffer overflow
+ STRCPY(tmp, infinity_str(f > 0.0, fmt_spec,
+ force_sign, space_for_positive));
+ str_arg_l = STRLEN(tmp);
+ zero_padding = 0;
+ }
+ else
+ {
+ if (isnan(f))
+ {
+ // Not a number: nan or NAN
+ STRCPY(tmp, ASCII_ISUPPER(fmt_spec) ? "NAN"
+ : "nan");
+ str_arg_l = 3;
+ zero_padding = 0;
+ }
+ else if (isinf(f))
+ {
+ STRCPY(tmp, infinity_str(f > 0.0, fmt_spec,
+ force_sign, space_for_positive));
+ str_arg_l = STRLEN(tmp);
+ zero_padding = 0;
+ }
+ else
+ {
+ // Regular float number
+ format[0] = '%';
+ l = 1;
+ if (force_sign)
+ format[l++] = space_for_positive ? ' ' : '+';
+ if (precision_specified)
+ {
+ size_t max_prec = TMP_LEN - 10;
+
+ // Make sure we don't get more digits than we
+ // have room for.
+ if ((fmt_spec == 'f' || fmt_spec == 'F')
+ && abs_f > 1.0)
+ max_prec -= (size_t)log10(abs_f);
+ if (precision > max_prec)
+ precision = max_prec;
+ l += sprintf(format + l, ".%d", (int)precision);
+ }
+ format[l] = fmt_spec == 'F' ? 'f' : fmt_spec;
+ format[l + 1] = NUL;
+
+ str_arg_l = sprintf(tmp, format, f);
+ }
+
+ if (remove_trailing_zeroes)
+ {
+ int i;
+ char *tp;
+
+ // Using %g or %G: remove superfluous zeroes.
+ if (fmt_spec == 'f' || fmt_spec == 'F')
+ tp = tmp + str_arg_l - 1;
+ else
+ {
+ tp = (char *)mnv_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 *)mnv_strchr((char_u *)tmp,
+ fmt_spec == 'e' ? 'e' : 'E');
+ if (tp != NULL && (tp[1] == '+' || tp[1] == '-')
+ && tp[2] == '0'
+ && mnv_isdigit(tp[3])
+ && mnv_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;
+
+ mnv_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;
+
+ mnv_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;
+
+ mnv_memset(str + str_l, ' ',
+ (size_t)pn > avail ? avail
+ : (size_t)pn);
+ }
+ str_l += pn;
+ }
+ }
+ mnv_free(tofree);
+ }
+ }
+
+ if (str_m > 0)
+ {
+ // make sure the string is nul-terminated even at the expense of
+ // overwriting the last character (shouldn't happen, but just in case)
+ //
+ str[str_l <= str_m - 1 ? str_l : str_m - 1] = '\0';
+ }
+
+ if (tvs != NULL && tvs[num_posarg != 0 ? num_posarg : arg_idx - 1].v_type != VAR_UNKNOWN)
+ emsg(_(e_too_many_arguments_to_printf));
+
+error:
+ mnv_free((char*)ap_types);
+ va_end(ap);
+
+ // Return the number of characters formatted (excluding trailing nul
+ // character), that is, the number of characters that would have been
+ // written to the buffer if it were large enough.
+ return (int)str_l;
+}
+
+#endif // PROTO