/* -*- mode: c++; indent-tabs-mode: nil -*- */
/*
    ql_string.qpp

    Qore Programming Language

    Copyright (C) 2003 - 2023 Qore Technologies, s.r.o.

    Permission is hereby granted, free of charge, to any person obtaining a
    copy of this software and associated documentation files (the "Software"),
    to deal in the Software without restriction, including without limitation
    the rights to use, copy, modify, merge, publish, distribute, sublicense,
    and/or sell copies of the Software, and to permit persons to whom the
    Software is furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in
    all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    DEALINGS IN THE SOFTWARE.

    Note that the Qore library is released under a choice of three open-source
    licenses: MIT (as above), LGPL 2+, or GPL 2+; see README-LICENSE for more
    information.
*/

#include <qore/Qore.h>
#include "qore/intern/ql_string.h"
#include "qore/intern/qore_number_private.h"

#include <cctype>
#include <cfloat>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <sys/stat.h>
#include <sys/types.h>

// for use in floating-point number formatting
// from: http://stackoverflow.com/questions/16839658/printf-width-specifier-to-maintain-precision-of-floating-point-value
#ifdef DBL_DECIMAL_DIG
  #define OP_DBL_Digs (DBL_DECIMAL_DIG)
#else
  #ifdef DECIMAL_DIG
    #define OP_DBL_Digs (DECIMAL_DIG)
  #else
    #define OP_DBL_Digs (DBL_DIG + 3)
  #endif
#endif

static int fix_num(QoreString& tmp, const QoreString& fmt, ExceptionSink* xsink) {
    assert(xsink);
    qore_offset_t off = fmt.getByteOffset(1, xsink);
    //printd(5, "fix_num tmp: '%s' fmt: '%s' off: %lld\n", tmp.getBuffer(), fmt.getBuffer(), off);
    if (*xsink)
        return -1;
    if (off < 0)
        return 0;

    // replace all thousands separators with nothing
    QoreString t(fmt.getBuffer(), off, fmt.getEncoding());
    tmp.replaceAll(t.getBuffer(), "");

    if (fmt.size() <= (size_t)off)
        return 0;

    t.clear();
    if (t.concat(fmt, 1, 1, xsink))
        return -1;

    if (t.size() == 1 && t[0] == '.')
        return 0;

    off = tmp.bindex(t.getBuffer(), 0);
    if (off < 0)
        return 0;

    tmp.replace(off, t.size(), ".");
    return 0;
}

static const char* memstr(const char* str, const char* pattern, size_t pl, size_t len) {
    while (true) {
        const char* p = (const char*)memchr(str, pattern[0], len);
        if (!p)
            return nullptr;

        //printd(5, "memstr() pattern: %s str: %p p: %p len: %d pl: %d remaining: %d (%c %c %c)\n", pattern, str, p,
        //  len, pl, len-(p-str), p[1], p[2], p[3]);

        // if there is not enough string left for the pattern, then return
        if ((len - (p - str)) < pl)
            return nullptr;

        bool found = true;
        for (size_t i = 1; i < pl; ++i) {
            if (pattern[i] != p[i]) {
                len -= (p - str + 1);
                str = p + 1;
                found = false;
                break;
            }
        }
        if (!found)
            continue;

        // found
        //printd(5, "memstr() got it! p: %p\n", p);
        return p;
    }
    return nullptr;
}

static void split_add_element(QoreListNode* l, const char* str, unsigned len, const QoreEncoding* enc) {
    if (enc) {
        l->push(new QoreStringNode(str, len, enc), nullptr);
    } else {
        BinaryNode* b = new BinaryNode;
        b->append(str, len);
        l->push(b, nullptr);
    }
}

QoreListNode* split_intern(const char* pattern, size_t pl, const char* str, size_t sl, const QoreEncoding* enc,
        bool with_separator) {
    QoreListNode* l = new QoreListNode(enc ? stringTypeInfo : binaryTypeInfo);
    const char* ostr = str;
    while (const char* p = memstr(str, pattern, pl, sl - (str - ostr))) {
        split_add_element(l, str, p - str + (with_separator ? pl : 0), enc);
        str = p + pl;
    }
    // add last field if there is data remaining
    if (sl - (str - ostr))
        split_add_element(l, str, sl - (str - ostr), enc);

    return l;
}

// count how many consecutive quotes
static size_t count_quotes(const char*& str, size_t& len, const QoreString& quote) {
    size_t rv = 0;
    while (len && len >= quote.size()) {
        if (!memcmp(quote.c_str(), str, quote.size())) {
            str += quote.size();
            len -= quote.size();
            ++rv;
            continue;
        }
        break;
    }
    return rv;
}

QoreListNode* split_with_quote(ExceptionSink* xsink, const QoreString* sep, const QoreString* str,
        const QoreString* quote, bool trim_unquoted, AbstractIteratorHelper* h, const QoreString* eol) {
    // convert pattern encoding to string encoding if necessary
    TempEncodingHelper pat(sep, str->getEncoding(), xsink);
    if (*xsink)
        return nullptr;

    // convert quote encoding to string encoding if necessary
    TempEncodingHelper tquote(quote, str->getEncoding(), xsink);
    if (*xsink)
        return nullptr;

    // in case the eol string needs encoding conversion
    TempEncodingHelper teol;

    // temporary string buffer in case additional lines need to be retrieved from a line iterator
    SimpleRefHolder<QoreStringNode> buf;

    //printd(5, "split_with_quote() sep: %s str: %s quote: %s trim_unquoted: %d\n", pat->c_str(), str->c_str(), tquote->c_str(), trim_unquoted);

    if (!tquote->strlen() || tquote->strlen() > sep->strlen())
        return split_intern(pat->c_str(), pat->strlen(), str->c_str(), str->strlen(), str->getEncoding());

    ReferenceHolder<QoreListNode> l(new QoreListNode(stringTypeInfo), xsink);
    const char* ostr = str->c_str();
    size_t sl = str->strlen();
    const char* tpattern = pat->c_str();
    size_t pl = pat->strlen();

    const char* ststr = ostr;

    // remaining byte length
    size_t len = sl;

    //printd(5, "split_with_quote() '%s' len: " QSD "\n", ststr, len);

    while (len) {
        // make sure len is always calculated correctly
        assert(len == (str->size() - (ststr - ostr)));

        size_t num_quotes = count_quotes(ststr, len, **tquote);
        if (num_quotes) {
            // see if we have empty quotes & we're at the end of the field
            if (num_quotes == 2 && (!len || (len >= pl && !memcmp(tpattern, ststr, pl)))) {
                // add an empty string to the list
                l->push(new QoreStringNode(str->getEncoding()), nullptr);

                if (!len) {
                    break;
                }

                ststr += pl;
                len -= pl;
                continue;
            } else if (!(num_quotes & 1)) {
                xsink->raiseException("SPLIT-ERROR", "field with text must begin with an add number of quotes (got "
                    QSD " quotes at the beginning of the field)", num_quotes);
                return nullptr;
            }

            // create the field string
            QoreStringNodeHolder field(new QoreStringNode(str->getEncoding()));

            // process double quotes to single quote
            while (num_quotes >= 2) {
                field->concat(*tquote, xsink);
                assert(!*xsink);
                num_quotes -= 2;
            }

            assert(num_quotes == 1);

            // find next quote character, ignore escaped and doubled quotes
            const char* p;
            while (len) {
                // make sure len is always calculated correctly
                assert(len == (str->size() - (ststr - ostr)));

                p = memstr(ststr, tquote->c_str(), tquote->strlen(), len);
                if (!p) {
                    // retrieve another line and add it to the current one if possible
                    if (h) {
                        // advance line index to add the next line to the current one
                        if (!h->next(xsink)) {
                            if (!*xsink) {
                                xsink->raiseException("SPLIT-ERROR", "cannot find closing quote '%s' in field " QSD
                                    " (starting with 1); no more line content in iterator", tquote->c_str(), l->size() + 1);
                            }
                        }
                        if (*xsink) {
                            return nullptr;
                        }
                        // get the line
                        ValueHolder v(h->getValue(xsink), xsink);
                        if (*xsink) {
                            return nullptr;
                        }
                        if (v->getType() != NT_STRING) {
                            xsink->raiseException("SPLIT-ERROR", "expecting iterator value type 'string'; got type " \
                                "'%s' instead", v->getTypeName());
                            return nullptr;
                        }
                        // set up the temporary line buffer if needed
                        if (!buf) {
                            buf = new QoreStringNode(*str);
                        }

                        // get ostr offset so we can recalculate after concatenating
                        size_t offset = ststr - ostr;
                        const QoreStringNode* new_line = v->get<const QoreStringNode>();
                        // add line separator
                        size_t eol_len;
                        if (!eol) {
                            eol_len = 1;
                            buf->concat('\n');
                        } else {
                            if (!teol && !teol.set(eol, str->getEncoding(), xsink)) {
                                assert(*xsink);
                                return nullptr;
                            }
                            buf->concat(teol->c_str());
                            eol_len = teol->size();
                        }
                        buf->concat(new_line, xsink);
                        if (*xsink) {
                            return nullptr;
                        }
                        len += (new_line->size() + eol_len);
                        sl += (new_line->size() + eol_len);
                        str = *buf;
                        ostr = buf->c_str();
                        ststr = ostr + offset;
                        //printd(5, "split_with_quote() got new line %p (" QLLD "): '%s' eol: '%s' (%d) ststr: '%s'\n", new_line, new_line->size(), new_line->c_str(), teol ? teol->c_str() : "<nl>", (int)eol_len, ststr);
                        assert((size_t)(ststr - ostr) == offset);
                        // make sure len is always calculated correctly
                        assert(len == (str->size() - (ststr - ostr)));
                        continue;
                    }

                    xsink->raiseException("SPLIT-ERROR", "cannot find closing quote '%s' in field " QSD " (starting with 1)", tquote->c_str(), l->size() + 1);
                    return nullptr;
                }
                //printd(5, "f " QSD " found closing quote in pos " QSD " (len " QSD ") str: '%s' ('%s')\n", l->size() + 1, p - ststr, len, ststr, field->c_str());

                assert(p != ststr);

                // add text up to the quote to the field
                size_t text_len = p - ststr;
                // if we have an escaped quote, do not copy the escaped quote to the string
                if (*(p - 1) == '\\') {
                    --text_len;
                }

                field->concat(ststr, text_len);
                {
                    QoreString fstr(ststr, text_len);
                    //printd(5, "f " QSD " adding text: '%s' (len " QSD ") ('%s')\n", l->size() + 1, fstr.c_str(), fstr.size(), field->c_str());
                }

                // skip the quote in the source string
                text_len += tquote->size();
                ststr += text_len;
                len -= text_len;

                // add escaped quote to field and keep searching
                if (*(p - 1) == '\\') {
                    field->concat(*tquote, xsink);
                    //printd(5, "f " QSD " adding escaped quote: %s ('%s')\n", l->size() + 1, tquote->c_str(), field->c_str());

                    assert(!*xsink);
                    // increment text pointer by escape char
                    ststr += 1;
                    len -= 1;
                    continue;
                }

                // if we have an odd number of quotes, then we have found the end of the field
                num_quotes = count_quotes(ststr, len, **tquote) + 1;

                //printd(5, "f " QSD " found " QSD " quote(s) ('%s')\n", l->size() + 1, num_quotes, field->c_str());

                // process double quotes to single quote
                while (num_quotes >= 2) {
                    field->concat(*tquote, xsink);
                    //printd(5, "f " QSD " adding quote: %s ('%s')\n", l->size() + 1, tquote->c_str(), field->c_str());
                    assert(!*xsink);
                    num_quotes -= 2;
                }

                if (num_quotes) {
                    assert(num_quotes == 1);
                    break;
                }
            }

            // or a separator string comes next
            if (len && (len < pl || memcmp(tpattern, ststr, pl))) {
                xsink->raiseException("SPLIT-ERROR", "separator pattern '%s' does not follow end quote in field "
                    QSD " (starting with 1)", tpattern, l->size() + 1);
                return nullptr;
            }

            // add the field to the list
            l->push(field.release(), nullptr);

            if (!len) {
                break;
            }
            ststr += pl;
            len -= pl;
            continue;
        }

        // no quotes
        assert(!num_quotes);

        // create the field string
        QoreStringNodeHolder field(new QoreStringNode(str->getEncoding()));

        const char* p = memstr(ststr, tpattern, pl, len);
        if (!p) {
            field->concat(ststr, len);
            QoreString fstr(ststr, len);
            //printd(5, "f " QSD " adding whole unquoted text: '%s' (len " QSD ") ('%s')\n", l->size() + 1, fstr.c_str(), fstr.size(), field->c_str());
        } else {
            field->concat(ststr, p - ststr);
            QoreString fstr(ststr, p - ststr);
            //printd(5, "f " QSD " adding unquoted text: '%s' (len " QSD ") ('%s')\n", l->size() + 1, fstr.c_str(), fstr.size(), field->c_str());
        }

        if (trim_unquoted && field->trim(xsink)) {
            return nullptr;
        }

        // add the new field
        l->push(field.release(), nullptr);
        if (!p) {
            break;
        }

        // advance the pointer
        size_t text_len = (p - ststr) + pl;
        ststr += text_len;
        len -= text_len;

        //printd(5, "f " QSD " now setting ststr: '%s' len: " QSD "\n", l->size() + 1, ststr, len);
    }

    return l.release();
}

QoreStringNode* join_intern(const QoreStringNode* p0, const QoreListNode* l, int offset, ExceptionSink* xsink) {
    assert(xsink);
    SimpleRefHolder<QoreStringNode> str(new QoreStringNode(p0->getEncoding()));

    for (unsigned i = offset; i < l->size(); i++) {
        QoreValue p = l->retrieveEntry(i);
        if (!p.isNothing()) {
            QoreStringValueHelper t(p, p0->getEncoding(), xsink);
            if (*xsink) {
                return nullptr;
            }

            str->concat(*t);
        }
        if (i < (l->size() - 1)) {
            str->concat(p0);
        }
    }

    return str.release();
}

QoreStringNode* format_float_intern(const QoreString& fmt, double num, ExceptionSink* xsink) {
   if (fmt.empty())
      return q_fix_decimal(new QoreStringNodeMaker("%.9g", num), 0);
   SimpleRefHolder<QoreStringNode> str(q_fix_decimal(new QoreStringNodeMaker("%.*f", OP_DBL_Digs + 6, num), 0));
   if (qore_number_private::formatNumberString(**str, fmt, xsink))
      return nullptr;
   return str.release();
}

QoreStringNode* format_float_intern(int prec, const QoreString& dsep, const QoreString& tsep, double num, ExceptionSink* xsink) {
   SimpleRefHolder<QoreStringNode> str(q_fix_decimal(new QoreStringNodeMaker("%.*f", OP_DBL_Digs + 6, num), 0));
   if (qore_number_private::formatNumberString(**str, prec, dsep, tsep, xsink))
      return nullptr;
   return str.release();
}

/** @defgroup regex_constants Regular Expression Constants
    The constants in this group can be combined with @ref bitwise_or_operator "binary or" to give regular expression options
    for the regex(), regex_subst(), and regex_extract() functions.
 */
///@{
//! Ignores case when matching regular expressions, equivalent to <tt>/i</tt>
const RE_Caseless = PCRE_CASELESS;

//! makes a dot (.) match a newline character, equivalent to /s
const RE_DotAll = PCRE_DOTALL;

//! ignores whitespace characters and enables comments prefixed by #, equivalent to <tt>/x</tt>
const RE_Extended = PCRE_EXTENDED;

//! makes start-of-line (^) or end-of-line ($) match after or before any newline in the subject string, equivalent to <tt>/m</tt>
const RE_MultiLine = PCRE_MULTILINE;

//! replace all matches globally in the string or extract all occurrences of the pattern(s) in the string, equivalent to <tt>/g</tt>
const RE_Global = QRE_GLOBAL;

//! extens Posix matches to the full Unicode character set
/** @since %Qore 0.9.4
*/
const RE_Unicode = PCRE_UCP;
///@}

/** @defgroup string_functions String Functions

    @section string_formatting String Formatting
    String formatting in %Qore is based on c-style "printf" string formatting.

    There are three types of objects in the format string:
    - Plain characters, which are copied as-is to the output string
    - Escape characters, which are converted and copied to the output string
    - Format specifications for arguments to be included in the output string, which are preceded by the percent sign
      \c "%"

    @note The percent sign \c "%" always starts a format specification unless it is followed by another \c "%" sign,
    in this case only one \c "%" is output (ie \c "%%" in the format string is output as a single \c "%")

    @subsection format_specification String Format Specification

    After the \c "%" sign, there can be zero or more formatting flags as in the following table:

    <b>Printf Formatting Flags</b>
    |!Flag|!Description
    |\c -|left-justify the field
    |\c +|include the sign for the number (+ or -)
    |\c 0|use zero left padding rather than space padding
    |<em>space</em>|use space padding

    Then a field width specifier can optionally be given as a string of digits specifying the length of the field.
    With "field" functions (function names preceded by \c "f_"), these width specifiers are hard limits; that is;
    arguments longer than the width specified are truncated to the specified width.

    @note String fields are padded based on the byte length of characters by default; to do string padding based on
    character width; use the \c "%w" specifier instead of \c "%s" (ex: \c "%10w");
    for "field" functions, if a multi-position character cannot fit in the space left in the field, the field is
    padded with \c "." characters; ex: @code f_sprintf("%-5w", str) == "ステ." @endcode

    For floating-point arguments, a precision specifier may be given by including a period "." and another digit
    string, which indicates the number of digits to appear after the decimal point.

    Then the format character is given as follows:

    <b>Printf Formatting Characters</b>
    |!Character|!Description
    |\c s|String where field width specify the number of characters regardless of their width
    |\c d|Integer, output in base 10 format
    |\c o|Integer, output in base 8 (octal) format
    |\c f|Literal floating point value
    |\c F|Literat floating point value
    |\c a|Hexadecimal floating-point output with lower-case \c "0x" and \c "abcdef"
    |\c A|Hexadecimal floating-point output with upper-case \c "0X" and \c "ABCDEF"
    |\c g|Compact floating-point output using a lower-case \c "e" for exponential output when necessary
    |\c G|Compact floating-point output using an upper-case \c "E" for exponential output when necessary
    |\c n|Any %Qore value, formatted as a string, without any line breaks
    |\c N|Any %Qore value, formatted as a string, with line breaks and whitespace formatting for complex objects
    |\c w|String where field widths specify actual character widths instead of the number of characters (ex: the character width of \c "ス" is 2)
    |\c x|Integer, output in base 16 (hexadecimal) format with lower-case a-f
    |\c X|Integer, output in base 16 (hexadecimal) format with upper-case A-F
    |\c y|Any %Qore value, formatted as a pseudo-YAML string, without any line breaks; this format option is similar to \c "%n", but uses a YAML-like format.  Note that the output is not guaranteed to be parsed by the yaml module's parseYAML() function; it is for display purposes only; to get real YAML functionality, use the yaml module.

    @anchor string_escape_chars
    <b>String Escape Characters</b>
    |!Escape Characters|!Description
    |\c \\b|a backspace character
    |\c \\f|a form-feed character
    |\c \\n|a newline character
    |\c \\r|a carriage-return character
    |\c \\t|a tab character
    |\c \\v|a vertical tab character
    |\c \\<em>num</em>|an 8-bit character whose value is the 1, 2, or 3 digit octal number \a num
    |\c \\\"|a double-quote character (\c \")
    |\c \\\\|a backslash character

    @warning Trying to escape a 3-digit octal number in the range 400-777 will result in a \c PARSE-EXCEPTION being thrown. Trying to parse a number starting with 8 or 9 will result in that number being reproduced literally.
    @code{.py}
string abc = "Number: \455"; # will throw a parse error
    @endcode
    @code{.py}
string abc = "Number: \820";
printf(abc); # will print out "Number: 820"
    @endcode

    @note Trying to escape any other character will result in that character being reproduced literally:
    @code{.py}
string abc = "He\llo\, worl\d!";
printf(abc); # will print out "Hello, world!"
    @endcode
 */
///@{

//! Returns the length in characters for the string passed
/** Note that the byte length may differ from the character length with multi-byte @ref character_encoding "character encodings". To get the byte length of a string, see strlen().

    @param str the string to return the character length of

    @return the length in characters for the string passed

    @par Example:
    @code{.py}
int len = length(str);
    @endcode

    @note
    - this operation has <i>O(n)</i> complexity if called on a string with a multi-byte @ref character_encoding "character encoding", otherwise it is <i>O(1)</i>
    - equivalent to <string>::length()

    @see
    - strlen()
    - <string>::strlen()
    - <string>::size()
 */
int length(softstring str) [flags=CONSTANT] {
   return str->length();
}

//! Returns the number of bytes in the binary object passed as an argument
/** This variant of the function accepting a binary object as an argument works identically to the @ref elements "elements operator"

    @param bin the binary object to return the size of

    @return the number of bytes in the binary object

    @par Example:
    @code{.py}
int nbytes = length(bin);
    @endcode
 */
int length(binary bin) [flags=CONSTANT] {
   return bin->size();
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
nothing length() [flags=RUNTIME_NOOP] {
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
int length(auto[doc] arg) [flags=NOOP] {
   return 0;
}

//! Returns the length in bytes of the string argument
/** Note that the byte length may differ from the character length with multi-byte @ref character_encoding "character encodings". For the character length of a string, see length().

    @param str the string to return the byte length of

    @return the length in bytes for the string passed

    @par Example:
    @code{.py}
int size = strlen(str);
    @endcode

    @note equivalent to <string>::strlen() and <string>::size()

    @see
    - length()
    - <string>::length()
 */
int strlen(softstring str) [flags=CONSTANT] {
   return str->strlen();
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
nothing strlen() [flags=RUNTIME_NOOP] {
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
int strlen(auto[doc] arg) [flags=NOOP] {
   return 0;
}

//! Returns the width of characters in the string; some unicode characters take up multiple spaces on output
/**
    @return the width of characters in the string; some unicode characters take up multiple spaces on output;
    for example the string "ステップの例" is made up of 6 double-wide characters, so the width is 12, while the length in
    characters is 6, and the size (string byte length) is 18

    @par Example:
    @code{.py}
int width = char_width(str);
    @endcode

    @note
    - this operation has <i>O(n)</i> complexity if the string has a multi-byte @ref character_encoding "character encoding", otherwise it is <i>O(1)</i>
    - equivalent to @ref <string>::width()

    @see
    - <string>::width()
    - strlen()
    - length()

    @since %Qore 0.9.4
*/
int char_width(softstring str) [flags=RET_VALUE_ONLY] {
    return str->getCharWidth(xsink);
}

//! Returns a string in all lower-case characters based on the argument passed
/** @par Example:
    @code{.py}
string lwr = tolower(str);
    @endcode

    This function operates on a very wide range of non-ASCII characters using a Unicode lookup table for mapping Latin, Cyrillic, Greek, Armenian, Georgian, etc characters.

    @param str a string to process

    @return a string in all lower-case characters based on the argument passed

    @note
    - equivalent to <string>::lwr(string)

    @see
    - <string>::lwr()
    - <string>::upr()
    - toupper()

    @since %Qore 0.8.8 this function operates on a wide range of characters and is no longer limited to ASCII characters
 */
string tolower(string str) [flags=CONSTANT] {
   SimpleRefHolder<QoreStringNode> rv(new QoreStringNode(str->getEncoding()));
   if (do_tolower(*(*rv), *str, xsink))
      return QoreValue();

   return rv.release();
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
nothing tolower() [flags=RUNTIME_NOOP] {
}

//! Returns a string in all upper-case characters based on the argument passed
/** @par Example:
    @code{.py}
string upr = toupper(str);
    @endcode

    This function operates on a very wide range of non-ASCII characters using a Unicode lookup table for mapping Latin, Cyrillic, Greek, Armenian, Georgian, etc characters.

    @param str a string to process

    @return a string in all upper-case characters based on the argument passed

    @note
    - equivalent to <string>::upr(string)

    @see
    - <string>::lwr()
    - <string>::upr()
    - tolower()

    @since %Qore 0.8.8 this function operates on a wide range of characters and is no longer limited to ASCII characters
 */
string toupper(string str) [flags=CONSTANT] {
   SimpleRefHolder<QoreStringNode> rv(new QoreStringNode(str->getEncoding()));
   if (do_toupper(*(*rv), *str, xsink))
      return QoreValue();

   return rv.release();
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
nothing toupper() [flags=RUNTIME_NOOP] {
}

//! Returns a portion of a string starting from an integer offset
/** Arguments can be negative, giving offsets from the end of the string. All offsets are character positions, not byte positions.

    @param str The string to process
    @param start The starting character for the substring where the first character is at offset 0; if the offset is negative, it designates the number of characters from the end of the string.  If the offset is 0, then the entire string is returned.

    @return the substring of the string starting from an integer character offset; the rest of the string is returned after this offset; an empty string is returned if the argument cannot be satisfied

    @par Example:
    @code{.py}
# get the last 10 characters of a string
string substr = substr(str, -10);
    @endcode

    @throw INVALID-ENCODING this exception could be thrown if a character offset calculation fails due to invalid encoding of multi-byte character data

    @note equivalent to <string>::substr(softint)
 */
string substr(softstring str, softint start) [flags=RET_VALUE_ONLY] {
   QoreStringNode* rv = str->substr(start, xsink);
   if (!rv)
      rv = new QoreStringNode(str->getEncoding());
   return rv;
}

//! Returns a portion of a string starting from an integer offset, with a length parameter
/** Arguments can be negative, giving offsets from the end of the string. All offsets are character positions, not byte positions.

    @param str The string to process
    @param start The starting character for the substring where the first character is at offset 0; if the offset is negative, it designates the number of characters from the end of the string
    @param len The maximum number of characters to copy; if this value is negative, the rest of the string from \a start will be copied to the substring, except without - \a len characters from the end of the string

    @return the substring of the string according to the arguments passed; an empty string is returned if the argument cannot be satisfied

    @par Example:
    @code{.py}
# get a substring 10 characters into the string except omitting the last 2 characters of the string
string substr = substr(str, 10, -2);
    @endcode

    @throw INVALID-ENCODING this exception could be thrown if a character offset calculation fails due to invalid encoding of multi-byte character data

    @note equivalent to <string>::substr(softint, softint)
 */
string substr(softstring str, softint start, softint len) [flags=RET_VALUE_ONLY] {
   QoreStringNode* rv = str->substr(start, len, xsink);
   if (!rv)
      rv = new QoreStringNode(str->getEncoding());
   return rv;
}

//! Returns a portion of a binary object starting from an integer offset
/** Arguments can be negative, giving offsets from the end of the data.

    @param b The binary object to process
    @param start The starting byte for the portion of the binary object where the first byte is at offset 0; if the offset is negative, it designates the number of bytes from the end of the data

    @return the portion of the binary data argument starting from an integer byte offset; the rest of the data is returned after this offset

    @par Example:
    @code{.py}
# get the last 10 bytes
binary b1 = substr(b, -10);
    @endcode

    @note equivalent to <binary>::substr(softint)

    @since %Qore 0.8.8
 */
binary substr(binary b, softint start) [flags=CONSTANT] {
   BinaryNode* b1 = new BinaryNode;
   b->substr(*b1, start);
   return b1;
}

//! Returns a portion of a binary object starting from an integer offset, with a length parameter
/** Arguments can be negative, giving offsets from the end of the data.

    @param b The binary object to process
    @param start The starting byte for the portion of the binary object where the first byte is at offset 0; if the offset is negative, it designates the number of bytes from the end of the data
    @param len The maximum number of bytes to copy; if this value is negative, the rest of the data from \a start will be copied to the return value, except without - \a len bytes from the end of the data

    @return the portion of the binary data argument according to the arguments passed

    @par Example:
    @code{.py}
# get a binary object 10 characters into the data except omitting the last 2 bytes
binary b1 = substr(b, 10, -2);
    @endcode

    @note equivalent to <binary>::substr(softint, softint)

    @since %Qore 0.8.8
 */
binary substr(binary b, softint start, softint len) [flags=CONSTANT] {
   BinaryNode* b1 = new BinaryNode;
   b->substr(*b1, start, len);
   return b1;
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
nothing substr() [flags=NOOP] {
}

//! Retrieves the character position of a substring within a string
/** The \a pos argument and the return value are in character positions; byte offsets may differ from the character offsets with multi-byte @ref character_encoding "character encodings".

    @param str the string to search in
    @param substr the substring to find in \a str; if the @ref character_encoding "character encoding" of this string does not match \a str, then it will be converted to <em>str</em>'s @ref character_encoding "character encoding" before processing
    @param pos the starting character position for the search

    @return the character position of a substring within a string, -1 is returned if the substring is not found

    @par Example:
    @code{.py}
int i = index(str, substr);
if (i == -1)
    printf("could not find %y in %y\n", substr, str);
    @endcode

    @throw ENCODING-CONVERSION-ERROR this exception could be thrown if the string arguments have different @ref character_encoding "character encodings" and an error occurs during encoding conversion
    @throw INVALID-ENCODING this exception could be thrown if a character offset calculation fails due to invalid encoding of multi-byte character data

    @note equivalent to <string>::find(softstring, softint)

    @see
    - <string>::rfind(softstring, softint)
    - rindex(softstring, softstring, softint)
    - bindex(softstring, softstring, softint)
    - brindex(softstring, softstring, softint)
 */
int index(softstring str, softstring substr, softint pos = 0) [flags=RET_VALUE_ONLY] {
   return str->index(*substr, pos, xsink);
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
int index() [flags=RUNTIME_NOOP] {
   return -1;
}

//! Retrieves the byte position of a substring within a string
/** The \a pos argument and the return value are in byte positions; byte offsets may differ from the character offsets with multi-byte @ref character_encoding "character encodings".

    @param str the string to search in
    @param substr the substring to find in \a str
    @param pos the starting character position for the search

    @return the byte position of a substring within a string, -1 is returned if the substring is not found

    @par Example:
    @code{.py}
int i = bindex(str, substr);
if (i == -1)
    printf("could not find %y in %y\n", substr, str);
    @endcode

    @note no character encoding conversions are made by this function even if the string arguments have different @ref character_encoding "character encodings"

    @see
    - <string>::find(softstring, softint)
    - <string>::rfind(softstring, softint)
    - index(softstring, softstring, softint)
    - rindex(softstring, softstring, softint)
    - brindex(softstring, softstring, softint)
 */
int bindex(softstring str, softstring substr, softint pos = 0) [flags=CONSTANT] {
   return str->bindex(*substr, (qore_offset_t)pos);
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
int bindex() [flags=RUNTIME_NOOP] {
   return -1;
}

//! Retrieves the character position of a substring within a string, starting the search from the end of the string
/** The \a pos argument and the return value are in character positions; byte offsets may differ from the character offsets with multi-byte @ref character_encoding "character encodings".

    @param str the string to search in
    @param substr the substring to find in \a str; if the @ref character_encoding "character encoding" of this string does not match \a str, then it will be converted to <em>str</em>'s @ref character_encoding "character encoding" before processing
    @param pos the starting character position for the search, -1 means start from the end of the string

    @return the character position of a substring within a string, -1 is returned if the substring is not found

    @par Example:
    @code{.py}
int i = rindex(str, substr);
if (i == -1)
    printf("could not find %y in %y\n", substr, str);
    @endcode

    @throw ENCODING-CONVERSION-ERROR this exception could be thrown if the string arguments have different @ref character_encoding "character encodings" and an error occurs during encoding conversion
    @throw INVALID-ENCODING this exception could be thrown if a character offset calculation fails due to invalid encoding of multi-byte character data

    @note equivalent to <string>::rfind(softstring, softint)

    @see
    - <string>::find(softstring, softint)
    - index(softstring, softstring, softint)
    - bindex(softstring, softstring, softint)
    - brindex(softstring, softstring, softint)
 */
int rindex(softstring str, softstring substr, softint pos = -1) [flags=RET_VALUE_ONLY] {
   return str->rindex(*substr, (qore_offset_t)pos, xsink);
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
int rindex() [flags=RUNTIME_NOOP] {
   return -1;
}

//! Retrieves the byte position of a substring within a string, starting the search from the end of the string
/** The \a pos argument and the return value are in byte positions; byte offsets may differ from the character offsets with multi-byte @ref character_encoding "character encodings".

    @param str the string to search in
    @param substr the substring to find in \a str
    @param pos the starting character position for the search, -1 means start from the end of the string

    @return the byte position of a substring within a string, -1 is returned if the substring is not found

    @par Example:
    @code{.py}
int i = brindex(str, substr);
if (i == -1)
    printf("could not find %y in %y\n", substr, str);
    @endcode

    @note no character encoding conversions are made by this function even if the string arguments have different @ref character_encoding "character encodings"

    @see
    - <string>::find(softstring, softint)
    - <string>::rfind(softstring, softint)
    - index(softstring, softstring, softint)
    - bindex(softstring, softstring, softint)
    - rindex(softstring, softstring, softint)
 */
int brindex(softstring str, softstring substr, softint pos = -1) [flags=CONSTANT] {
   return str->brindex(*substr, (qore_offset_t)pos);
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
int brindex() [flags=RUNTIME_NOOP] {
   return -1;
}

//! Gives the positive numeric value of the given byte in the string passed; if no string is passed or the offset is after the end of the string, -1 is returned
/**
    @param str the string containing the byte to be retrieved
    @param offset the byte offset of the byte to be retrieved

    @return the positive numeric value of the given byte in the string passed; if the offset is after the end of the string or negative, -1 is returned

    @par Example:
    @code{.py}
int i = ord(str);
    @endcode

    @note ord() only works on byte offsets and returns byte values
 */
int ord(softstring str, softint offset = 0) [flags=CONSTANT] {
   if (offset < 0 || (size_t)offset >= str->strlen())
      return -1;

   return (unsigned char)str->c_str()[offset];
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
int ord() [flags=RUNTIME_NOOP] {
   return 0;
}

//! Returns a string containing a single ASCII character represented by the numeric value passed
/** @param val the byte value of the character; only the least-significant byte is used
    @param encoding the character encoding tag for the string return value; if not present, the @ref default_encoding "default character encoding" is assumed

    @return a string containing a single ASCII character represented by the numeric value passed

    @par Example:
    @code{.py}
string char = chr(13);
    @endcode

    @since %Qore 0.8.5 the encoding argument is supported
 */
string chr(softint val, *string encoding) [flags=CONSTANT] {
   const QoreEncoding *qe = encoding ? QEM.findCreate(encoding) : QCS_DEFAULT;
   QoreStringNode* rv = new QoreStringNode(qe);
   rv->concat((char)val);
   return rv;
}

//! This function variant returns a string with a single ASCII NULL ('\\0'); it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/** @return a string with a single ASCII NULL ('\\0')
 */
string chr(auto[doc] arg) [flags=NOOP] {
   return new QoreStringNode('\0');
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
nothing chr() [flags=RUNTIME_NOOP] {
}

//! Splits a string into a list of components based on a separator string
/**
    @param sep the separator string; if the separator string is not found in the string to split, then a list with
    only one element containing the entire string argument is returned'; if this string has a different
    @ref character_encoding "character encoding" than \a str, then it will be converted to <em>str</em>'s
    @ref character_encoding "character encoding"
    @param str the string to split
    @param with_separator include the separator string in every element

    @return a list of each component of a string separated by a separator string, with the separator removed; the
    separator pattern will not be included in the elements of the list returned unless the \a with_separator argument
    is @ref True

    @par Example:
    @code{.py}
list<string> l = split(":", "some:text:here"); # returns ("some", "text", "here")
    @endcode

    @throw ENCODING-CONVERSION-ERROR this exception could be thrown if the string arguments have different
    @ref character_encoding "character encodings" and an error occurs during encoding conversion

    @note equivalent to <string>::split(string, bool)
 */
list<string> split(string sep, string str, bool with_separator = False) [flags=RET_VALUE_ONLY] {
    // convert pattern encoding to string if necessary
    TempEncodingHelper temp(sep, str->getEncoding(), xsink);
    if (*xsink) {
        return QoreValue();
    }

    return split_intern(temp->c_str(), temp->strlen(), str->c_str(), str->strlen(), str->getEncoding(),
        with_separator);
}

//! Splits a string into a list of components based on a separator string and a quote character
/** The quote character can appear as the first part of a field, in which case it is assumed to designate the entire
    field. If instances of the quote character are found in the field doubled or preceded by a backquote character
    (<tt>\\</tt>), then these quote characters are included as part of the field's text and not treated as quote
    characters (in quoted fields only).

    @param sep the separator string; if the separator string is not found in the string to split, then a list with
    only one element containing the entire string argument is returned'; if this string has a different
    @ref character_encoding "character encoding" than \a str, then it will be converted to <em>str</em>'s
    @ref character_encoding "character encoding"
    @param str the string to split
    @param quote the quote character; quotes may be escaped (in quoted fields only) by doubling the quote character or
    by preceding it with a backslash (<tt>\\</tt>)
    @param trim_unquoted remove leading and trailing whitespace from unquoted fields

    @return a list of each component of a string separated by a separator string, with the separator and any enclosing
    quote characters removed

    @par Example:
    @code{.py}
list<string> l = split(",", "some,'text with spaces',here", "'"); # returns ("some", "text with spaces", "here")
    @endcode

    @throw ENCODING-CONVERSION-ERROR this exception could be thrown if the string arguments have different
    @ref character_encoding "character encodings" and an error occurs during encoding conversion
    @throw SPLIT-ERROR field missing closing quote character; extra text following quoted field

    @note equivalent to <string>::split(string, string)

    @since
    - %Qore 0.8.6 the \c trim_unquoted parameter was added
    - %Qore 0.9.4 processes doubled quote characters as an escaped quote within a quoted field
 */
list<string> split(string sep, string str, string quote, bool trim_unquoted = False) [flags=RET_VALUE_ONLY] {
    return split_with_quote(xsink, sep, str, quote, trim_unquoted);
}

//! Returns a list of binary objects representing each component of the binary object separated by the bytes identified by the separator argument, with the separator removed
/**
    @param data the binary object to separate
    @param sep the bytes that separate the fields

    @return a list of binary objects representing each component of the binary object separated by the bytes identified by the separator argument, with the separator removed

    @par Example:
    @code{.py}
list<binary> l = split(sep, bin);
    @endcode

    @note equivalent to <binary>::split(data)
 */
list<binary> split(data sep, binary data) [flags=CONSTANT] {
    const char* bin_ptr;
    size_t bin_len;
    q_get_data(sep, bin_ptr, bin_len);
    return split_intern(bin_ptr, bin_len, (const char*)data->getPtr(), data->size(), nullptr);
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
list<string> split() [flags=RUNTIME_NOOP] {
   return new QoreListNode(stringTypeInfo);
}

//! Returns a string describing the character encoding of the string argument passed
/**
    @param str the string to get the encoding for

    @return a string describing the character encoding of the string argument passed

    @par Example:
    @code{.py}
string enc = get_encoding(str);
    @endcode

    @note equivalent to <string>::encoding()
 */
string get_encoding(string str) [flags=CONSTANT] {
   return new QoreStringNode(str->getEncoding()->getCode());
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
nothing get_encoding() [flags=RUNTIME_NOOP] {
}

//! Performs explicit string character encoding conversions
/**
    @param str the string to convert
    @param encoding the encoding for the return value

    @return a string corresponding to the string argument converted to the encoding given

    @par Example:
    @code{.py}
string enc = convert_encoding(str, "iso-8859-1");
    @endcode

    @throw ENCODING-CONVERSION-ERROR this exception could be thrown if the string arguments have different @ref character_encoding "character encodings" and an error occurs during encoding conversion
 */
string convert_encoding(string str, string encoding) [flags=RET_VALUE_ONLY] {
   return str->convertEncoding(QEM.findCreate(encoding), xsink);
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
nothing convert_encoding() [flags=RUNTIME_NOOP] {
}

//! Returns the first string argument tagged with the character encoding given as the second argument; does not actually change the string data
/** Use only in the case that a string is tagged with the wrong encoding, for example, if a string from a @ref Qore::File "File" object has a different encoding than the @ref Qore::File "File" object.

    @param str the string to be returned
    @param encoding the encoding to tag the return value with

    @return the first string argument tagged with the character encoding given as the second argument; does not actually change the string data

    @par Example:
    @code{.py}
string nstr = force_encoding(str, "iso-8859-1");
    @endcode
 */
string force_encoding(string str, string encoding) [flags=CONSTANT] {
   return new QoreStringNode(str->c_str(), str->strlen(), QEM.findCreate(encoding));
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
nothing force_encoding() [flags=RUNTIME_NOOP] {
}

//! Returns @ref True if the regular expression matches the string passed, otherwise returns @ref False
/** Strings are converted to UTF-8 for pattern-matching; if any invalid encodings are encountered, an ENCODING-CONVERSION-ERROR is raised

    @param str the string to test
    @param regex the regular expression pattern
    @param options regular expression options; see @ref regex_constants for possible values

    @return @ref True if the regular expression matches the string passed, otherwise returns @ref False

    @par Example:
    @code{.py}
bool b = regex("hello", "^hel"); # returns True
    @endcode

    @throw REGEX-COMPILATION-ERROR There was an error compiling the regular expression
    @throw REGEX-OPTION-ERROR the option argument contains invalid option bits
    @throw ENCODING-CONVERSION-ERROR this exception could be thrown if an encoding error is encountered when converting the given strings to UTF-8

    @note equivalent to <string>::regex(string, int)

    @see @ref qore_regex for more information about regular expression support in Qore
 */
bool regex(string str, string regex, int options = 0) [flags=RET_VALUE_ONLY] {
   QoreRegex qr(*regex, options, xsink);
   if (*xsink)
      return QoreValue();

   return qr.exec(str, xsink);
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
nothing regex() [flags=RUNTIME_NOOP] {
}

//! Returns a string with patterns substituted according to the arguments passed
/** Strings are converted to UTF-8 for pattern-matching; if any invalid encodings are encountered, an ENCODING-CONVERSION-ERROR is raised

    @param str the source string for substitution
    @param regex the regular expression pattern string
    @param subst the pattern for substitution; use \c $1, \c $2, etc for patterns in parentheses in the \a regex argument
    @param options regular expression options; see @ref regex_constants for possible values

    @return a string with patterns substituted according to the arguments passed

    @par Example:
    @code{.py}
regex_subst("hello there", "^(.*) there$", "you $1"); #returns "you hello"
    @endcode

    @par Example of global replacement:
    @code{.py}
string content = "123
123
123";

printf("%s\n", regex_subst(content, "123", "456", Qore::RE_Global));

resulting in:
456
456
456
    @endcode

    @throw REGEX-COMPILATION-ERROR There was an error compiling the regular expression
    @throw REGEX-OPTION-ERROR the option argument contains invalid option bits
    @throw ENCODING-CONVERSION-ERROR this exception could be thrown if an encoding error is encountered when converting the given strings to UTF-8

    @see
    - replace()
    - @ref qore_regex for more information about regular expression support in Qore
 */
string regex_subst(string str, string regex, string subst, int options = 0) [flags=RET_VALUE_ONLY] {
   bool global;
   if (options & QRE_GLOBAL) {
      global = true;
      options &= 0xffffffff;
   }
   else
      global = false;

   QoreRegexSubst qrs(regex, (int)options, xsink);
   if (*xsink)
      return QoreValue();

   if (global)
      qrs.setGlobal();
   return qrs.exec(str, subst, xsink);
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
nothing regex_subst() [flags=RUNTIME_NOOP] {
}

//! Returns a list of substrings in a string based on matching patterns defined by a regular expression
/** Strings are converted to UTF-8 for pattern-matching; if any invalid encodings are encountered, an ENCODING-CONVERSION-ERROR is raised

    @param str the string to process
    @param regex the regular expression to use for matching, elements should be given in parentheses
    @param options regular expression options; see @ref regex_constants for possible values

    @return a list of substrings in a string based on matching patterns defined by a regular expression or @ref nothing if no match was made

    @par Example:
    @code{.py}
*list<*string> rv = regex_extract("ns:element", "(\\w+):(\\w+)");
    @endcode

    @throw REGEX-COMPILATION-ERROR There was an error compiling the regular expression
    @throw REGEX-OPTION-ERROR the option argument contains invalid option bits
    @throw ENCODING-CONVERSION-ERROR this exception could be thrown if an encoding error is encountered when converting the given strings to UTF-8

    @note equivalent <string>::regexExtract(string, int)

    @see @ref qore_regex for more information about regular expression support in Qore

    @since %Qore 0.8.8 this function accepts the @ref Qore::RE_Global option to extract all occurrences of the pattern(s) in a string
 */
*list<*string> regex_extract(string str, string regex, int options = 0) [flags=RET_VALUE_ONLY] {
   QoreRegex qr(*regex, options, xsink);
   if (*xsink)
      return QoreValue();

   return qr.extractSubstrings(str, xsink);
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
nothing regex_extract() [flags=RUNTIME_NOOP] {
}

//! Replaces all occurrences of a substring in a string with another string
/**
    @param str the string to process
    @param source the substring to replace; if this string has a different
    @ref character_encoding "character encoding" than \a str, then it will be converted to <em>str</em>'s
    @ref character_encoding "character encoding"
    @param target the replacement value for \a source; if this string has a different
    @ref character_encoding "character encoding" than \a str, then it will be converted to <em>str</em>'s
    @ref character_encoding "character encoding"
    @param start the starting character position in the source for the replacement where the first character is at
    position 0 (may not be the same as the byte position for multibyte @ref character_encoding "character encodings");
    negative numbers give character offsets from the end of the string
    @param end the ending character position in the source for the replacement where the first character is at position 0 (may not be the same as the byte position for multibyte @ref character_encoding "character encodings"; negative numbers give offsets from the end of the string)

    @return a string with all occurrences of \a source replaced with \a target

    @par Example:
    @code{.py}
string str = replace("hello there", "there", "you"); # returns "hello you"
    @endcode

    @throw ENCODING-CONVERSION-ERROR this exception could be thrown if the string arguments have different @ref character_encoding "character encodings" and an error occurs during encoding conversion

    @note no regular expressions are used in this function, only direct replacements

    @see regex_subst()
 */
string replace(string str, string source, string target, int start = 0, int end = -1) [flags=RET_VALUE_ONLY] {
    const QoreEncoding* ccs = str->getEncoding();

    TempEncodingHelper t1(source, ccs, xsink);
    if (*xsink) {
        return QoreValue();
    }

    if (t1->empty() || str->empty()) {
        return str->refSelf();
    }

    TempEncodingHelper t2(target, ccs, xsink);
    if (*xsink) {
        return QoreValue();
    }

    SimpleRefHolder<QoreStringNode> nstr(new QoreStringNode(ccs));

    const char* cstr = str->c_str();
    size_t plen = t1->size();

    // only execute the replace logic if there is a pattern
    if (plen) {
        const char* pattern = t1->c_str();

        bool has_length = false;
        size_t length = 0;

        if (start) {
            if (start < 0) {
                length = str->length();
                has_length = true;
                start = length + start;
            }
            const char* cn = cstr + str->size();
            size_t i = ccs->getByteLen(cstr, cn, start, xsink);
            if (*xsink) {
                return QoreValue();
            }
            if (i == str->size()) {
                return str->refSelf();
            }
            // add skipped beginning bytes to target string
            nstr->concat(cstr, i);
            // reposition source ptr
            cstr += i;
        }

        size_t size = str->size();

        if (end) {
            if (end < 0) {
                if (!has_length) {
                    length = str->length();
                }
                end = length + end;
            }
            const char* cn = str->c_str() + str->size();
            // get the byte length to the character after the requested one
            size_t i = ccs->getByteLen(str->c_str(), cn, end + 1, xsink);
            if (*xsink) {
                return QoreValue();
            }
            if (start > 0 && end < start) {
                return str->refSelf();
            }
            //printd(5, "end: %d\n", (int)i);

            size = i;
        }

        //printd(5, "cstr: %p str: %p size: %d cstr - str: %d\n", cstr, str->c_str(), (int)size, (int)(cstr - str->c_str()));
        assert(static_cast<size_t>(cstr - str->c_str()) < size);
        while (const char* p = (const char*)q_memmem(cstr, size - (cstr - str->c_str()), pattern, plen)) {
            //printd(5, "str: %p p: %p '%s' '%s'->'%s'\n", cstr, p, cstr, pattern, t2->c_str());
            if (p != cstr) {
                nstr->concat(cstr, p - cstr);
            }
            nstr->concat(*t2);
            cstr = p + plen;
        }
    }

    // add last field
    if (*cstr) {
        nstr->concat(cstr);
    }

    return nstr.release();
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
nothing replace() [flags=RUNTIME_NOOP] {
}

//! Creates a string from separator string and a list of arguments
/**
    @param str the separator string
    @param ... the list of arguments to join; each argument will be converted to a string if necessary to be concatenated to the return value string; additionally if any string argument has a different @ref character_encoding "character encoding" than \a str, then it will be converted to <em>str</em>'s @ref character_encoding "character encoding" before concatentation to the return value string

    @return a string created from a list and a seperator string, each element in the list will be present in the return value separated by the separator string

    @par Example:
    @code{.py}
string str = join(":", "a", "b", "c"); # returns "a:b:c"
    @endcode

    @throw ENCODING-CONVERSION-ERROR this exception could be thrown if the string arguments have different @ref character_encoding "character encodings" and an error occurs during encoding conversion
 */
string join(string str, ...) [flags=RET_VALUE_ONLY] {
   return join_intern(str, args, 1, xsink);
}

//! Creates a string from separator string and a list of arguments
/**
    @param str the separator string
    @param l the list of arguments to join; each argument will be converted to a string if necessary to be concatenated to the return value string; additionally if any string argument has a different @ref character_encoding "character encoding" than \a str, then it will be converted to <em>str</em>'s @ref character_encoding "character encoding" before concatentation to the return value string

    @return a string created from a list and a separator string, each element in the list will be present in the return value separated by the separator string; the string returned with have the same @ref character_encoding "character encoding" as \a str

    @par Example:
    @code{.py}
string str = join(":", ("a", "b", "c")); # returns "a:b:c"
    @endcode

    @throw ENCODING-CONVERSION-ERROR this exception could be thrown if the string arguments have different @ref character_encoding "character encodings" and an error occurs during encoding conversion

    @note equivalent to <list>::join(string)
 */
string join(string str, list<auto> l) [flags=RET_VALUE_ONLY] {
   return join_intern(str, l, 0, xsink);
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
nothing join() [flags=RUNTIME_NOOP] {
}

//! Removes the trailing end-of-line indicator (\c "\n" or \c "\r\n") from a string and returns the new string (also see the @ref chomp "chomp operator")
/** If no EOL indicator is present in the string, this function simply returns the original string unmodified.

    @param str the string to process

    @return the new string with any end-of-line character(s) removed

    @par Example:
    @code{.py}
string line = chomp("hello\n"); # returns "hello"
    @endcode

    @see the @ref chomp "chomp operator"
 */
string chomp(string str) [flags=CONSTANT] {
   QoreStringNode* rv = str->copy();
   rv->chomp();
   return rv;
}

//! Removes the trailing end-of-line indicator (\c "\n" or \c "\r\n") from a reference to a string and returns the new string (also see the @ref chomp "chomp operator")
/** This variant accepts variable references, in which case it will modify the string in place and also return the modified string

    @param str a reference to an lvalue continaing the string value to process; if the lvalue does not have a string value @ref nothing is returned

    @return the string as modified

    @par Example:
    @code{.py}
chomp(\str);
    @endcode
 */
*string chomp(reference<string> str) {
   QoreTypeSafeReferenceHelper ref(str, xsink);
   if (!ref || ref.getType() != NT_STRING)
      return QoreValue();

   QoreStringNode* rv = reinterpret_cast<QoreStringNode* >(ref.getUnique(xsink));
   if (*xsink)
      return QoreValue();

   rv->chomp();
   return rv->refSelf();
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
nothing chomp() [flags=RUNTIME_NOOP] {
}

//! Removes byte characters from the start and end of a string and returns the new string (also see the @ref trim "trim operator")
/** By default (if the second argument is omitted or passed as an empty string) the following whitespace byte characters are removed: \c " ", \c "\n", \c "\r", \c "\t", \c "\v" (vertical tab, ASCII 11), and \c "\0" (null character). To trim other characters, pass a string as the second argument specifying the byte characters to be removed.

    @param str the string to trim
    @param chars the characters to trim from the start and end of the string (default:  \c " ", \c "\n", \c "\r", \c "\t", \c "\v" (vertical tab, ASCII 11), and \c "\0" (null character))

    @return the trimmed string

    @par Example:
    @code{.py}
string tstr = trim(str);
    @endcode
 */
string trim(string str, *string chars) [flags=CONSTANT] {
   SimpleRefHolder<QoreStringNode> rv(str->copy());
   return rv->trim(xsink, chars) ? QoreValue() : rv.release();
}

//! Removes byte characters from the start and end of a reference to an lvalue containing a string and returns string after processing (also see the @ref trim "trim operator")
/** By default (if the second argument is omitted or passed as an empty string) the following whitespace byte characters are removed: \c " ", \c "\n", \c "\r", \c "\t", \c "\v" (vertical tab, ASCII 11), and \c "\0" (null character). To trim other characters, pass a string as the second argument specifying the byte characters to be removed.

    This variant accepts variable references, in which case it will modify the string in place and also return the modified string.

    @param str a reference to an lvalue continaing the string value to process; if the lvalue does not have a string value @ref nothing is returned
    @param chars the characters to trim from the start and end of the string (default:  \c " ", \c "\n", \c "\r", \c "\t", \c "\v" (vertical tab, ASCII 11), and \c "\0" (null character))

    @return the trimmed string

    @par Example:
    @code{.py}
trim(\str);
    @endcode
 */
*string trim(reference<string> str, *string chars) {
   QoreTypeSafeReferenceHelper ref(str, xsink);
   if (!ref || ref.getType() != NT_STRING)
      return QoreValue();

   QoreStringNode* rv = reinterpret_cast<QoreStringNode*>(ref.getUnique(xsink));
   if (*xsink)
      return QoreValue();

   return rv->trim(xsink, chars) ? QoreValue() : rv->refSelf();
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
nothing trim() [flags=RUNTIME_NOOP] {
}

//! Removes byte characters from the start of a string and returns the new string
/** By default (if the second argument is omitted or passed as an empty string) the following whitespace byte characters are removed: \c " ", \c "\n", \c "\r", \c "\t", \c "\v" (vertical tab, ASCII 11), and \c "\0" (null character). To trim other characters, pass a string as the second argument specifying the byte characters to be removed.

    @param str the string to trim
    @param chars the characters to trim from the start of the string (default:  \c " ", \c "\n", \c "\r", \c "\t", \c "\v" (vertical tab, ASCII 11), and \c "\0" (null character))

    @return the trimmed string

    @par Example:
    @code{.py}
string tstr = ltrim(" ABC ");
# returns string "ABC "
    @endcode
 */
string ltrim(string str, *string chars) [flags=CONSTANT] {
   SimpleRefHolder<QoreStringNode> rv(str->copy());
   return rv->trimLeading(xsink, chars) ? QoreValue() : rv.release();
}

//! Removes byte characters from the start of a reference to an lvalue containing a string and returns string after processing
/** By default (if the second argument is omitted or passed as an empty string) the following whitespace byte characters are removed: \c " ", \c "\n", \c "\r", \c "\t", \c "\v" (vertical tab, ASCII 11), and \c "\0" (null character). To trim other characters, pass a string as the second argument specifying the byte characters to be removed.

    This variant accepts variable references, in which case it will modify the string in place and also return the modified string.

    @param str a reference to an lvalue containing the string value to process; if the lvalue does not have a string value @ref nothing is returned
    @param chars the characters to trim from the start of the string (default:  \c " ", \c "\n", \c "\r", \c "\t", \c "\v" (vertical tab, ASCII 11), and \c "\0" (null character))

    @return the trimmed string

    @par Example:
    @code{.py}
ltrim(\str);
    @endcode
 */
*string ltrim(reference<string> str, *string chars) {
   QoreTypeSafeReferenceHelper ref(str, xsink);
   if (!ref || ref.getType() != NT_STRING)
      return QoreValue();

   QoreStringNode* rv = reinterpret_cast<QoreStringNode*>(ref.getUnique(xsink));
   if (*xsink)
      return QoreValue();

   return rv->trimLeading(xsink, chars) ? QoreValue() : rv->refSelf();
}

//! Removes byte characters from the end of a string and returns the new string
/** By default (if the second argument is omitted or passed as an empty string) the following whitespace byte characters are removed: \c " ", \c "\n", \c "\r", \c "\t", \c "\v" (vertical tab, ASCII 11), and \c "\0" (null character). To trim other characters, pass a string as the second argument specifying the byte characters to be removed.

    @param str the string to trim
    @param chars the characters to trim from the end of the string (default:  \c " ", \c "\n", \c "\r", \c "\t", \c "\v" (vertical tab, ASCII 11), and \c "\0" (null character))

    @return the trimmed string

    @par Example:
    @code{.py}
string tstr = rtrim(str);
# returns string " ABC"
    @endcode

    @bug it is not possible to trim multi-byte characters from strings using this function; each byte is treated as a character.  No encoding conversions are done even if the \a chars argument has a different @ref character_encoding "character encoding" than the \a str argument.
 */
string rtrim(string str, *string chars) [flags=CONSTANT] {
   SimpleRefHolder<QoreStringNode> rv(str->copy());
   return rv->trimTrailing(xsink, chars) ? QoreValue() : rv.release();
}

//! Removes byte characters from the end of a reference to an lvalue containing a string and returns string after processing
/** By default (if the second argument is omitted or passed as an empty string) the following whitespace byte characters are removed: \c " ", \c "\n", \c "\r", \c "\t", \c "\v" (vertical tab, ASCII 11), and \c "\0" (null character). To trim other characters, pass a string as the second argument specifying the byte characters to be removed.

    This variant accepts variable references, in which case it will modify the string in place and also return the modified string.

    @param str a reference to an lvalue containing the string value to process; if the lvalue does not have a string value @ref nothing is returned
    @param chars the characters to trim from the end of the string (default:  \c " ", \c "\n", \c "\r", \c "\t", \c "\v" (vertical tab, ASCII 11), and \c "\0" (null character))

    @return the trimmed string

    @par Example:
    @code{.py}
rtrim(\str);
    @endcode

    @bug it is not possible to trim multi-byte characters from strings using this function; each byte is treated as a character.  No encoding conversions are done even if the \a chars argument has a different @ref character_encoding "character encoding" than the \a str argument.
 */
*string rtrim(reference<string> str, *string chars) {
   QoreTypeSafeReferenceHelper ref(str, xsink);
   if (!ref || ref.getType() != NT_STRING)
      return QoreValue();

   QoreStringNode* rv = reinterpret_cast<QoreStringNode*>(ref.getUnique(xsink));
   if (*xsink)
      return QoreValue();

   return rv->trimTrailing(xsink, chars) ? QoreValue() : rv->refSelf();
}

//! Returns a truncated string with no more than the given number of bytes and optionally converted to a specific @ref character_encoding "character encoding"
/**
    @param str the string to truncate
    @param len the maximum byte length of the string
    @param encoding if given, the string returned will be returned in the given encoding, otherwise the encoding of \ str is used for the return value

    @return the string truncated to be no longer than the given byte length in the @ref character_encoding "character encoding" given (or in the original string's @ref character_encoding "character encoding" if no \a encoding argument was passed to the function); if the string has a multi-byte @ref character_encoding "character encoding", this function will guarantee that the returned string ends on a valid character (in this case the byte length of the string could be less than the limit given)

    @par Example:
    @code{.py}
string column = trunc_str(str, 80, dsp.getOSEncoding());
    @endcode

    @throw ENCODING-CONVERSION-ERROR this exception could be thrown if an explicit @ref character_encoding "character encoding" was given and an error occurs during encoding conversion
 */
string trunc_str(softstring str, softint len, *string encoding) [flags=RET_VALUE_ONLY] {
   const QoreEncoding* enc = encoding ? QEM.findCreate(encoding) : str->getEncoding();
   if (len <= 0)
      return new QoreStringNode(enc);

   TempEncodingHelper tmp(str, enc, xsink);
   if (!tmp)
      return QoreValue();

   if (tmp->strlen() < (size_t)len) {
      len = tmp->strlen();
      return new QoreStringNode(tmp.giveBuffer(), len, len + 1, enc);
   }

   if (!enc->isMultiByte())
      return new QoreStringNode(tmp->c_str(), len, enc);

   // find position of last character fitting in len bytes
   const char* p = tmp->c_str();
   int64 sl = 0;
   while (true) {
      qore_offset_t size = enc->getCharLen(p, len - sl);
      if (size <= 0 || ((sl + size) > len))
         break;
      sl += size;
      p += size;
   }

   return new QoreStringNode(tmp->c_str(), sl, enc);
}

//! Returns a new string with a repeated string element and optionally removing trailing characters
/**
    @param str the string element to repeat in the return value string
    @param smul the number of times the element should repeat
    @param offset the number of characters that should be removed from the end of the string after processing

    @par Example:
    @code{.py}
string ret = strmul("%s,", 3, 1); # returns "%s,%s,%s"
    @endcode

    @throw STRMUL-ERROR \a offset is < 0 or \a smul < 0
 */
string strmul(softstring str, softint smul, *softint offset) [flags=RET_VALUE_ONLY] {
   if (offset < 0)
      return xsink->raiseException("STRMUL-ERROR", "Offset argument has to be >= 0; value: " QLLD, offset);

   if (smul < 0)
      return xsink->raiseException("STRMUL-ERROR", "Multiple argument has to be >= 0; value: " QLLD, smul);

   QoreStringNodeHolder ret(new QoreStringNode);
   while (smul > 0) {
      ret->concat(str);
      --smul;
   }

   if (offset) {
      if (offset > (int64)ret->length())
         return xsink->raiseException("STRMUL-ERROR", "Offset (" QLLD ") is greater than string length (" QSD ")", offset, ret->length());
      ret->splice(ret->length() - offset, xsink);
      if (*xsink)
         return QoreValue();
   }

   return ret.release();
}

//! Returns a string of a formatted number according to a number argument and a format string
/** @param fmt the format string has the following format:\n
    <em>\<thousands_separator\></em><tt>[</tt><em>\<decimal_separator\>[\<decimals\>]</em><tt>]</tt>\n
    where:\n
    - \a thousands_separator and \a decimal_separator are single characters defining the thousands and decimal separator characters respectively, and
    - \a decimals is one or more digits defining how may decimals should appear after the decimal point
    @param num the number to format

    @return a string of a formatted number according to a number argument and a format string; if the format string does not follow the given format, then the number without formatting is returned

    @par Example:
    @code{.py}
string nstr = format_number(".,3", -48392093894.2349); # returns "-48.392.093.894,235"
    @endcode

    @see <float>::format()
 */
string format_number(string fmt, softfloat num) [flags=CONSTANT] {
   return format_float_intern(*fmt, num, xsink);
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
nothing format_number() [flags=RUNTIME_NOOP] {
}

//! Reverses a string and returns the new string
/** Works properly on strings with multi-byte @ref character_encoding "character encodings" as well (such as UTF-8)

    @param str the string to reverse

    @return a string with all characters in reverse order

    @par Example:
    @code{.py}
string ns = reverse(str);
    @endcode

    @see reverse(list)
*/
string reverse(softstring str) [flags=CONSTANT] {
   return str->reverse();
}

//! Outputs a string to standard output with no formatting
/**
    @param ... each of the arguments passed to this function will be output literally with no output formatting

    @par Example:
    @code{.py}
print("hello\n");
    @endcode

    @see printf() for a function that allows for formatted output
 */
nothing print(...) [dom=TERMINAL_IO] {
   if (!args)
      return QoreValue();

   for (unsigned i = 0; i < args->size(); i++)
      print_node(stdout, get_param_value(args, i));
}

//! Returns a formatted string based on a @ref string_formatting "format string" and other arguments; enforces field widths on arguments larger than the given field width
/**
    This function will truncate output longer than any field width given in the @ref string_formatting "format string"

    @param fmt the @ref string_formatting "format string"
    @param ... the argument(s) corresponding to format specifiers in the format string

    @return a formatted string corresponding to the arguments given

    @par Example:
    @code{.py}
string str = f_sprintf("%5s", "a long string"); # will return "a lon"
    @endcode

    @see
    - @ref string_formatting for information on the formatting string
    - sprintf() for a similar function that does not enforce field widths
 */
string f_sprintf(string[doc] fmt, ...) [flags=CONSTANT] {
   return q_sprintf(args, 1, 0, xsink);
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
string f_sprintf() [flags=NOOP] {
   return null_string();
}

//! Returns a formatted string based on a @ref string_formatting "format string" and other arguments; does not enforce field widths on arguments larger than the given field width
/**
    This function will not truncate output longer than any field width given in the @ref string_formatting "format string"

    @param fmt the @ref string_formatting "format string"
    @param ... the argument(s) corresponding to format specifiers in the format string

    @return a formatted string corresponding to the arguments given

    @par Example:
    @code{.py}
string str = sprintf("%5s", "a long string"); # will return "a long string"
    @endcode

    @see
    - @ref string_formatting for information on the formatting string
    - f_sprintf() for a similar function that enforces field widths
 */
string sprintf(string[doc] fmt, ...) [flags=CONSTANT] {
   return q_sprintf(args, 0, 0, xsink);
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
string sprintf() [flags=NOOP] {
   return null_string();
}

//! Returns a formatted string based on a @ref string_formatting "format string" and other arguments given as a list after the format string; enforces field widths on arguments larger than the given field width
/**
    This function will truncate output longer than any field width given in the @ref string_formatting "format string"

    @param fmt the @ref string_formatting "format string"
    @param varg the argument(s) corresponding to format specifiers in the format string

    @return a formatted string corresponding to the arguments given

    @par Example:
    @code{.py}
string str = f_vsprintf("%5s %3d\n", ("a lon", 500)); # will return "a lon 500"
    @endcode

    @see
    - @ref string_formatting for information on the formatting string
    - vsprintf() for a similar function that does not enforce field widths

    @since %Qore 0.8.4
 */
string f_vsprintf(string[doc] fmt, auto[doc] varg) [flags=CONSTANT] {
   return q_vsprintf(args, 1, 0, xsink);
}

//! Returns a formatted string based on a @ref string_formatting "format string" and other arguments given as a list after the format string; does not enforce field widths on arguments larger than the given field width
/**
    This function will not truncate output longer than any field width given in the @ref string_formatting "format string"

    @param fmt the @ref string_formatting "format string"
    @param varg the argument(s) corresponding to format specifiers in the format string

    @return a formatted string corresponding to the arguments given

    @par Example:
    @code{.py}
string str = vsprintf("%5s %3d\n", ("a long string", 5000)); # will return "a long string 5000"
    @endcode

    @see
    - @ref string_formatting for information on the formatting string
    - f_vsprintf() for a similar function that enforces field widths
 */
string vsprintf(string[doc] fmt, auto[doc] varg) [flags=CONSTANT] {
   return q_vsprintf(args, 0, 0, xsink);
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
string vsprintf() [flags=NOOP] {
   return null_string();
}

//! Outputs the string passed to standard output, using the first argument as a @ref string_formatting "format string"; enforces field widths on arguments larger than the given field width
/**
    This function will truncate output longer than any field width given in the @ref string_formatting "format string"

    @param fmt the @ref string_formatting "format string"
    @param ... the argument(s) corresponding to format specifiers in the format string

    @return the formatted string output corresponding to the arguments given

    @par Example:
    @code{.py}
f_printf("%5s", "a long string"); # will print out "a lon"
    @endcode

    @see
    - @ref string_formatting for information on the formatting string
    - printf() for a similar function that does not enforce field widths
 */
string f_printf(string[doc] fmt, ...) [dom=TERMINAL_IO] {
   AbstractQoreNode* node;
   print_node(stdout, node = q_sprintf(args, 1, 0, xsink));
   return node;
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
string f_printf() [flags=NOOP] {
   return null_string();
}

//! Outputs the string passed to standard output, using the first argument as a @ref string_formatting "format string"; does not enforce field widths on arguments larger than the given field width
/**
    This function will not truncate output longer than any field width given in the @ref string_formatting "format string"

    @param fmt the @ref string_formatting "format string"
    @param ... the argument(s) corresponding to format specifiers in the format string

    @return the formatted string output corresponding to the arguments given

    @par Example:
    @code{.py}
printf("%5s", "a long string"); # will print out "a long string"
    @endcode

    @see
    - @ref string_formatting for information on the formatting string
    - f_printf() for a similar function that enforces field widths
 */
string printf(string[doc] fmt, ...) [dom=TERMINAL_IO] {
   AbstractQoreNode* node;
   print_node(stdout, node = q_sprintf(args, 0, 0, xsink));
   return node;
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
string printf() [flags=NOOP;dom=TERMINAL_IO] {
   return null_string();
}

//! Outputs the string passed to standard output, using the first argument as a @ref string_formatting "format string" and a second argument giving a list or a single argument to the format string; enforces field widths on arguments larger than the given field width
/**
    This function will truncate output longer than any field width given in the @ref string_formatting "format string"

    @param fmt the @ref string_formatting "format string"
    @param varg the argument(s) corresponding to format specifiers in the format string

    @return the formatted string output corresponding to the arguments given

    @par Example:
    @code{.py}
f_vprintf("%5s %3d", ("a long string", 5000)); # will print out "a lon 500"
    @endcode

    @see
    - @ref string_formatting for information on the formatting string
    - vprintf() for a similar function that does not enforce field widths

    @since %Qore 0.8.4
 */
string f_vprintf(string[doc] fmt, auto[doc] varg) [dom=TERMINAL_IO] {
   AbstractQoreNode* node;
   print_node(stdout, node = q_vsprintf(args, 1, 0, xsink));
   return node;
}

//! Outputs the string passed to standard output, using the first argument as a @ref string_formatting "format string" and a second argument giving a list or a single argument to the format string; does not enforce field widths on arguments larger than the given field width
/**
    This function will not truncate output longer than any field width given in the @ref string_formatting "format string"

    @param fmt the @ref string_formatting "format string"
    @param varg the argument(s) corresponding to format specifiers in the format string

    @return the formatted string output corresponding to the arguments given

    @par Example:
    @code{.py}
vprintf("%5s %3d", ("a long string", 5000)); # will print out "a long string 5000"
    @endcode

    @see
    - @ref string_formatting for information on the formatting string
    - f_vprintf() for a similar function that enforces field widths
 */
string vprintf(string[doc] fmt, auto[doc] varg) [dom=TERMINAL_IO] {
   AbstractQoreNode* node;
   print_node(stdout, node = q_vsprintf(args, 0, 0, xsink));
   return node;
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
string vprintf() [flags=NOOP] {
   return null_string();
}

//! Flushes output to the console output with print(), printf(), etc
/**
    @par Example:
    @code{.py}
flush();
    @endcode
 */
nothing flush() [dom=TERMINAL_IO] {
   fflush(stdout);
}

//! tries to parse a string value as a boolean
/** @par Example:
    @code{.py}
bool b = parse_boolean(str);
    @endcode

    @param str case-insensitive \c "on", \c "true", \c "enable*", \c "yes", \c "y" are @ref True "True", the rest is interpreted as a number where 0 is @ref False "False", everything else is  @ref True "True"

    @return a boolean value according to the parsed argument

    @since Qore 0.8.12 \c "y" is also parsed as @ref True "True"
 */
bool parse_boolean(string str) [flags=CONSTANT] {
   return q_parse_bool(str->c_str());
}

//! returns the first value passed as a boolean
/** @par Example:
    @code{.py}
bool b = parse_boolean(i);
    @endcode

    @param val the value to convert to a boolean

    @return the first value passed as a boolean
 */
bool parse_boolean(any val) [flags=CONSTANT] {
   return val.getAsBool();
}

//! returns a number corresponding to the string and the argument, giving the format
/** @par Example:
    @code{.py}
number n = parse_number("1.150.200,55", ".,");
    @endcode

    @param str the string to parse
    @param fmt the format of the number in the string, consisting of two characters, the first for the thousands separator, the second for the decimal point (ex: \c ".," for many European locales)

    @return the number value represented by the string

    @see
    - @ref parse_int()
    - @ref parse_float()

    @since %Qore 0.8.11.1
*/
number parse_number(string str, string fmt) [flags=RET_VALUE_ONLY] {
   TempEncodingHelper tfmt(fmt, str->getEncoding(), xsink);
   if (*xsink)
      return QoreValue();

   QoreString tmp(*str);
   if (fix_num(tmp, **tfmt, xsink))
      return QoreValue();

   return new QoreNumberNode(tmp.getBuffer());
}

//! returns a floating-point value corresponding to a string representing the number and an argument giving the format
/** @par Example:
    @code{.py}
float f = parse_float("1.150.200,55", ".,");
    @endcode

    @param str the string to parse
    @param fmt the format of the number in the string, consisting of two characters, the first for the thousands separator, the second for the decimal point (ex: \c ".," for many European locales)

    @return the floating-point value represented by the string

    @see
    - @ref parse_number()
    - @ref parse_int()

    @since %Qore 0.8.11.1
*/
float parse_float(string str, string fmt) [flags=RET_VALUE_ONLY] {
   TempEncodingHelper tfmt(fmt, str->getEncoding(), xsink);
   if (*xsink)
      return QoreValue();

   QoreString tmp(*str);
   if (fix_num(tmp, **tfmt, xsink))
      return QoreValue();

   return q_strtod(tmp.getBuffer());
}

//! returns an integer corresponding to a string representing the number and an argument giving the format
/** @par Example:
    @code{.py}
int i = parse_int("1.150.200,55", ".,");
    @endcode

    @param str the string to parse
    @param fmt the format of the number in the string, consisting of two characters, the first for the thousands separator, the second for the decimal point (ex: \c ".," for many European locales)

    @return the number value represented by the string

    @see
    - @ref parse_number()
    - @ref parse_float()

    @since %Qore 0.8.13
*/
int parse_int(string str, string fmt) [flags=RET_VALUE_ONLY] {
   TempEncodingHelper tfmt(fmt, str->getEncoding(), xsink);
   if (*xsink)
      return QoreValue();

   QoreString tmp(*str);
   if (fix_num(tmp, **tfmt, xsink))
      return QoreValue();

   return strtoll(tmp.getBuffer(), 0, 10);
}
///@}
