diff src/strings.c @ 32670:695b50472e85

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