# Source: String.js

    /**
     * @license
     * Copyright 2020 Roberto Luiz Souza Monteiro,
     *                Renata Souza Barreto,
     *                Hernane Borges de Barros Pereira.
     *
     * Licensed under the Apache License, Version 2.0 (the 'License');
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *   http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an 'AS IS' BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */

    /*
     * The sprintf function implemented in this library is based on the JavaScript library sprintf-js,
     * distributed under the following license. The original source code can be obtained from the repository:
     * https://github.com/alexei/sprintf.js.git
     *
     * Copyright (c) 2007-present, Alexandru Mărășteanu <hello@alexei.ro>
     * All rights reserved.
     * Redistribution and use in source and binary forms, with or without
     * modification, are permitted provided that the following conditions are met:
     * * Redistributions of source code must retain the above copyright
     * notice, this list of conditions and the following disclaimer.
     * * Redistributions in binary form must reproduce the above copyright
     * notice, this list of conditions and the following disclaimer in the
     * documentation and/or other materials provided with the distribution.
     * Neither the name of this software nor the names of its contributors may be
     * used to endorse or promote products derived from this software without
     * specific prior written permission.
     *
     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
     * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
     * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     * DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
     * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
     * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
     * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     */

    /**
     * MaiaScript string library.
     * @class
     */
    function MaiaString() {
        // Regular expressions used by sprintf the parser.
        this.re = {
            not_string: /[^s]/,
            not_bool: /[^t]/,
            not_type: /[^T]/,
            not_primitive: /[^v]/,
            number: /[diefg]/,
            numeric_arg: /[bcdiefguxX]/,
            json: /[j]/,
            not_json: /[^j]/,
            text: /^[^\x25]+/,
            modulo: /^\x25{2}/,
            placeholder: /^\x25(?:([1-9]\d*)\$|\(([^)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-gijostTuvxX])/,
            key: /^([a-z_][a-z_\d]*)/i,
            key_access: /^\.([a-z_][a-z_\d]*)/i,
            index_access: /^\[(\d+)\]/,
            sign: /^[+-]/
        }

        init();

        /**
         * Creates the attributes of the class.
         */
        function init() {
            // Class attributes goes here.
        }

        /**
         * Convert an string into camel case object name format.
         * @param {string}   str - String to convert.
         * @param {string}   firstCharToUpperCase - Converts the first character to uppercase.
         * @return {string}  The string converted to camel case.
         */
        this.camelize = function(str, firstCharToUpperCase) {
            function matchChars(match, index) {
                if (+match == 0) {
                    return "";
                } else {
                    return ((index == 0) && !firstCharToUpperCase) ? match.toLowerCase() : match.toUpperCase();
                }
            }
            return str.replace(/(?:^\w|[A-Z]|\b\w|\s+)/g, matchChars);
        }

        /**
         * Formats a string based on format specifiers passed to the function.
         * @param {string}   fmt - A string containing format specifiers.
         * @param {object}   arguments - Objects to be formatted.
         * @return {string}  A formatted string based on format specifiers passed to the function.
         */
        this.sprintf = function(fmt) {
            /*
                * Functions with variable number of arguments, use the variable 'arguments'
                * to contain the arguments passed to the function.
                */
            return this.sprintFormat(this.sprintfParse(fmt), arguments);
        }

        /**
         * Formats a string based on format specifiers passed to the function.
         * @param {string}   fmt - A string containing format specifiers.
         * @param {array}    argv - Array containing objects to be formatted.
         * @return {string}  A formatted string based on format specifiers passed to the function.
         */
        this.vsprintf = function(fmt, argv) {
            return this.sprintf.apply(null, [fmt].concat(argv || []));
        }

        /**
         * Formats a string based on an abstract synthetic tree produced by the format specifier compiler.
         * @param {object}   parseTree - Abstract synthetic tree produced by the format specifier compiler.
         * @param {array}    argv - Array containing objects to be formatted.
         * @return {string}  A formatted string based on format specifiers passed to the function.
         */
        this.sprintFormat = function(parseTree, argv) {
            var cursor = 1, treeLength = parseTree.length, arg, output = '', i, k, ph, pad, padCharacter, padLength, isPositive, sign;
            for (i = 0; i < treeLength; i++) {
                if (typeof parseTree[i] === 'string') {
                    output += parseTree[i];
                } else if (typeof parseTree[i] === 'object') {
                    ph = parseTree[i]
                    if (ph.keys) {
                        arg = argv[cursor]
                        for (k = 0; k < ph.keys.length; k++) {
                            if (arg == undefined) {
                                throw new Error(this.sprintf('Function sprintf cannot access property "%s" of undefined value "%s"', ph.keys[k], ph.keys[k-1]))
                            }
                            arg = arg[ph.keys[k]];
                        }
                    } else if (ph.param_no) {
                        arg = argv[ph.param_no];
                    } else {
                        arg = argv[cursor++];
                    }
                    if (this.re.not_type.test(ph.type) && this.re.not_primitive.test(ph.type) && arg instanceof Function) {
                        arg = arg();
                    }
                    if (this.re.numeric_arg.test(ph.type) && (typeof arg !== 'number' && isNaN(arg))) {
                        throw new TypeError(this.sprintf('Function sprintf expecting number but found %T', arg));
                    }
                    if (this.re.number.test(ph.type)) {
                        isPositive = arg >= 0;
                    }
                    switch (ph.type) {
                        case 'b':
                            arg = parseInt(arg, 10).toString(2);
                            break;
                        case 'c':
                            arg = String.fromCharCode(parseInt(arg, 10));
                            break;
                        case 'd':
                        case 'i':
                            arg = parseInt(arg, 10);
                            break;
                        case 'j':
                            arg = JSON.stringify(arg, null, ph.width ? parseInt(ph.width) : 0);
                            break;
                        case 'e':
                            arg = ph.precision ? parseFloat(arg).toExponential(ph.precision) : parseFloat(arg).toExponential();
                            break;
                        case 'f':
                            arg = ph.precision ? parseFloat(arg).toFixed(ph.precision) : parseFloat(arg);
                            break;
                        case 'g':
                            arg = ph.precision ? String(Number(arg.toPrecision(ph.precision))) : parseFloat(arg);
                            break;
                        case 'o':
                            arg = (parseInt(arg, 10) >>> 0).toString(8);
                            break;
                        case 's':
                            arg = String(arg);
                            arg = (ph.precision ? arg.substring(0, ph.precision) : arg);
                            break
                        case 't':
                            arg = String(!!arg);
                            arg = (ph.precision ? arg.substring(0, ph.precision) : arg);
                            break
                        case 'T':
                            arg = Object.prototype.toString.call(arg).slice(8, -1).toLowerCase();
                            arg = (ph.precision ? arg.substring(0, ph.precision) : arg);
                            break;
                        case 'u':
                            arg = parseInt(arg, 10) >>> 0;
                            break;
                        case 'v':
                            arg = arg.valueOf();
                            arg = (ph.precision ? arg.substring(0, ph.precision) : arg);
                            break;
                        case 'x':
                            arg = (parseInt(arg, 10) >>> 0).toString(16);
                            break;
                        case 'X':
                            arg = (parseInt(arg, 10) >>> 0).toString(16).toUpperCase();
                            break;
                    }
                    if (this.re.json.test(ph.type)) {
                        output += arg;
                    } else {
                        if (this.re.number.test(ph.type) && (!isPositive || ph.sign)) {
                            sign = isPositive ? '+' : '-';
                            arg = arg.toString().replace(this.re.sign, '');
                        } else {
                            sign = '';
                        }
                        padCharacter = ph.pad_char ? ph.pad_char === '0' ? '0' : ph.pad_char.charAt(1) : ' ';
                        padLength = ph.width - (sign + arg).length;
                        pad = ph.width ? (padLength > 0 ? padCharacter.repeat(padLength) : '') : '';
                        output += ph.align ? sign + arg + pad : (padCharacter === '0' ? sign + pad + arg : pad + sign + arg);
                    }
                }
            }
            return output;
        }

        var sprintfCache = Object.create(null);

        /**
         * Compiles a string based on the syntactic rules of the C sprintf function.
         * @param {string}   fmt - A string containing format specifiers.
         * @return {object}  Abstract synthetic tree produced for the format specifier.
         */
        this.sprintfParse = function(fmt) {
            if (sprintfCache[fmt]) {
                return sprintfCache[fmt];
            }
            var formatString = fmt, match, parseTree = [], argNames = 0;
            while (formatString) {
                if ((match = this.re.text.exec(formatString)) !== null) {
                    parseTree.push(match[0]);
                } else if ((match = this.re.modulo.exec(formatString)) !== null) {
                    parseTree.push('%');
                } else if ((match = this.re.placeholder.exec(formatString)) !== null) {
                    if (match[2]) {
                        argNames |= 1;
                        var fieldList = [], replacementField = match[2], fieldMatch = [];
                        if ((fieldMatch = this.re.key.exec(replacementField)) !== null) {
                            fieldList.push(fieldMatch[1]);
                            while ((replacementField = replacementField.substring(fieldMatch[0].length)) !== '') {
                                if ((fieldMatch = this.re.key_access.exec(replacementField)) !== null) {
                                    fieldList.push(fieldMatch[1]);
                                } else if ((fieldMatch = this.re.index_access.exec(replacementField)) !== null) {
                                    fieldList.push(fieldMatch[1]);
                                } else {
                                    throw new SyntaxError('Function sprintf failed to parse named argument key');
                                }
                            }
                        } else {
                            throw new SyntaxError('Function sprintf failed to parse named argument key');
                        }
                        match[2] = fieldList;
                    } else {
                        argNames |= 2;
                    }
                    if (argNames === 3) {
                        throw new Error('Function sprintf mixing positional and named placeholders is not (yet) supported');
                    }
                    parseTree.push(
                        {
                            placeholder: match[0],
                            param_no:    match[1],
                            keys:        match[2],
                            sign:        match[3],
                            pad_char:    match[4],
                            align:       match[5],
                            width:       match[6],
                            precision:   match[7],
                            type:        match[8]
                        }
                    );
                } else {
                    throw new SyntaxError('Function sprintf unexpected placeholder');
                }
                formatString = formatString.substring(match[0].length);
            }
            return sprintfCache[fmt] = parseTree;
        }
    }

    string = new MaiaString();