# Source: Core.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.
     */

    /**
     * MaiaScript core library.
     * @class
     */
    function Core() {
        /*
         * This property needs to be updated
         * with each new version of MaiaStudio.
         */
        this.version = "3.9.0";

        this.testResult = {
            "expected": {},
            "obtained": {}
        };

        init();

        /**
         * Creates the attributes of the class.
         */
        function init() {
            // Class attributes goes here.
            compiledCode = {
                "xml": "",
                "mil": "",
                "js": ""
            }
        }

        /*
         * The following functions allow you to manipulate MaiaScript objects.
         */

        /**
         * Copies a matrix.
         * @param {array}  obj - Matrix to be copied.
         * @return {array}  A copy of the matrix.
         */
        this.copyMatrix = function(obj) {
            var newMatrix = [];
            for (var i = 0; i < obj.length; i++) {
                newMatrix[i] = obj[i].slice();
            }
            return newMatrix;
        }

        /**
         * Returns the character at the indicated position.
         * @param {string}   str - The string to look for.
         * @param {number}   pos - The character position.
         * @return {string}  The character at the indicated position.
         */
        this.charAt = function(str, pos) {
            return str.charAt(pos);
        }

        /**
         * Returns the character code at the indicated position.
         * @param {string}   str - The string to look for.
         * @param {number}   pos - The character position.
         * @return {string}  The character code at the indicated position.
         */
        this.charCodeAt = function(str, pos) {
            return str.charCodeAt(pos);
        }

        /**
         * Returns a clone of an object.
         * @param {object}   obj - The object to be cloned.
         * @return {string}  The clone of the object.
         */
        this.clone = function(obj) {
            return Object.assign({}, obj);
        }

        /**
         * Join two matrices.
         * @param {array}   mtx1 - The first matrix.
         * @param {array}   mtx2 - The second matrix.
         * @return {array}  Matrix containing the two indicated matrices.
         */
        this.concat = function(mtx1, mtx2) {
            return mtx.concat(mtx1, mtx2);
        }

        /**
         * Calculates the conjugate of a complex number.
         * @param {number}   num - The complex number.
         * @return {number}  the conjugate of a complex number.
         */
        this.conj = function(num) {
            var res;
            if (core.type(num) == 'complex') {
                res = core.complex(core.toNumber(num.real), -core.toNumber(num.imaginary));
            } else {
                throw new Error('The object passed to the conj() function is not a complex number, in the expression conj(' + core.toString(num) + ').');
            }
            return res;
        }

        /**
         * Returns a complex number, given the real and imaginary part of the number.
         * @param {object}   real - TThe real part of the complex number.
         * @param {number}   img - The imaginary part of the complex number.
         * @return {number}  A complex complex number.
         */
        this.complex = function(real, img) {
            var num;
            if ((typeof real == 'number') && (typeof img == 'number')) {
                num = {
                    "real": real,
                    "imaginary": img
                }
            } else {
                throw new Error('It is necessary to provide the real and imaginary parts of the number, in the expression complex(' + real + ',' + img + ').');
            }
            return num;
        }

        /**
         * Returns a Date object.
         * @return {object}  A Date object.
         */
        this.date = function() {
            return new Date();
        }

        /**
         * Calculates the determinant matrix.
         * @param {object}  obj - The matrix to calculate the determinant.
         * @return {array}  A (rows x columns) matrix.
         */
        this.det = function(obj) {
            var mtx = [];
            if (core.type(obj) == 'matrix') {
                var dim = core.dim(obj);
                var m = dim[0];
                var n = dim[1];
                // Convert to the diagonal equivalent matrix.
                var cpy = this.copyMatrix(obj);
                mtx = core.ident(m);
                for (var j = 0; j < m; j++) {
                    if (cpy[j][j] != 0) {
                        for (var i = 0; i < m; i++) {
                            if (i != j) {
                                var scale = -cpy[i][j] / cpy[j][j];
                                for (k = j; k < n; k++) {
                                    cpy[i][k] = cpy[i][k] + scale * cpy[j][k];
                                }
                                for (k = 0; k < n; k++) {
                                    mtx[i][k] = mtx[i][k] + scale * mtx[j][k];
                                }
                            }
                        }
                    }
                }
                for (i = 0; i < m; i++) {
                    for (j = 0; j < n; j++) {
                        mtx[i][j] = mtx[i][j] / cpy[i][i];
                    }
                }
                // Calculates the determinant of the matrix.
                var det = 1;
                for (i = 0; i < m; i++) {
                    det = det * cpy[i][i];
                }
            } else {
                throw new Error('The argument for function det() must be a matrix, in the expression det(' + core.toString(obj) + ').');
            }
            return det;
        }

        /**
         * Calculates the diagonal equivalent matrix.
         * @param {object}  obj - The matrix to calculate the diagonal equivalent matrix.
         * @return {array}  A (rows x columns) matrix.
         */
        this.diag = function(obj) {
            if (core.type(obj) == 'matrix') {
                var dim = core.dim(obj);
                var m = dim[0];
                var n = dim[1];
                // Convert to the diagonal equivalent matrix.
                var cpy = this.copyMatrix(obj);
                for (var j = 0; j < m; j++) {
                    if (cpy[j][j] != 0) {
                        for (var i = 0; i < m; i++) {
                            if (i != j) {
                                var scale = -cpy[i][j] / cpy[j][j];
                                for (k = j; k < n; k++) {
                                    cpy[i][k] = cpy[i][k] + scale * cpy[j][k];
                                }
                            }
                        }
                    }
                }
                // Calculates the determinant of the matrix.
                var det = 1;
                for (i = 0; i < m; i++) {
                    det = det * cpy[i][i];
                }
                if (det == 0) {
                    throw new Error('The matrix is singular, in the expression diag(' + core.toString(obj) + ').');
                }
            } else {
                throw new Error('The argument for function diag() must be a matrix, in the expression diag(' + core.toString(obj) + ').');
            }
            return cpy;
        }

        /**
         * Returns the dimensions of an array.
         * @param {array}   obj - Object to be measured.
         * @return {array}  Array containing the dimensions of a matrix.
         */
        this.dim = function(obj) {
            var arrayDimensions = [];
            if (Array.isArray(obj)) {
                arrayDimensions.push(obj.length);
                if (Array.isArray(obj[0])) {
                    var elementDimension = this.dim(obj[0]);
                    if (typeof elementDimension != 'undefined') {
                        arrayDimensions = arrayDimensions.concat(elementDimension);
                    }
                }
            }
            return arrayDimensions;
        }

        /**
         * Evaluates a MaiaScript script.
         * @param {string}   stript - The script to be evaluated.
         * @param {object}   namespace - The namespace where evaluate the script.
         * @return {number}  Result of the evaluated script.
         */
        this.eval = function(script, namespace) {
            var result;

            compiledCode.xml = "";

            function getXml(data) {
                compiledCode.xml += data;
            }
            var s = new MaiaScript.XmlSerializer(getXml, true);
            var maiaScriptParser = new MaiaScript(script, s);
            try {
                maiaScriptParser.parse_maiascript();
            } catch (pe) {
                if (!(pe instanceof maiaScriptParser.ParseException)) {
                    throw pe;
                } else {
                    var parserError = maiaScriptParser.getErrorMessage(pe);
                    alert(parserError);
                    throw parserError;
                }
            }
            var parser = new DOMParser();
            var xml = parser.parseFromString(compiledCode.xml, "text/xml");

            var compiler = new MaiaCompiler();
            compiledCode.js = compiler.compile(xml);
            try {
                if (typeof namespace != 'undefined') {
                    result = eval(namespace, compiledCode.js);
                } else {
                    result = eval(compiledCode.js);
                }
                result = eval(compiledCode.js);
            } catch (e) {
                var evalError = e.message;
                system.log(evalError);
            }
            return result;
        }

        /**
         * Creates the identity matrix..
         * @param {number}  rows - Number of rows in the matrix.
         * @return {array}  A (rows x rows) identity matrix.
         */
        this.ident = function(rows) {
            var mtx = core.matrix(0, rows, rows);
            for (var i = 0; i < rows; i++) {
                mtx[i][i] = 1;
            }
            return mtx;
        }

        /**
         * Returns the imaginary part of a complex number.
         * @param {object}   obj - The complex number.
         * @return {number}  The imaginary part of a complex number.
         */
        this.imaginary = function(obj) {
            var num;
            if (typeof obj == 'object') {
                if ('imaginary' in obj) {
                    num = obj.imaginary;
                } else {
                    throw new Error('The object is not a complex number, in the expression imaginary(' + core.toString(obj) + ').');
                }
            } else {
                throw new Error('The object is not a complex number, in the expression imaginary(' + core.toString(obj) + ').');
            }
            return num;
        }

        /**
         * Returns true if one string is contained in another or in an array.
         * @param {object}    obj - The string containing the other one.
         * @param {string}    text - Search string.
         * @return {boolean}  True if one string is contained in another or in an array.
         */
        this.includes = function(obj, text) {
            return obj.includes(text);
        }

        /**
         * Returns the position of one string in another.
         * @param {string}   str - The string containing the other one.
         * @param {string}   text - Search string.
         * @return {number}  The position of one string in the other.
         */
        this.indexOf = function(str, text) {
            return str.indexOf(text);
        }

        /**
         * Calculates the inverse matrix.
         * @param {object}  obj - The matrix to calculate the inverse.
         * @return {array}  A (rows x columns) matrix.
         */
        this.inv = function(obj) {
            var mtx = [];
            if (core.type(obj) == 'matrix') {
                var dim = core.dim(obj);
                var m = dim[0];
                var n = dim[1];
                // Convert to the diagonal equivalent matrix.
                var cpy = this.copyMatrix(obj);
                mtx = core.ident(m);
                for (var j = 0; j < m; j++) {
                    if (cpy[j][j] != 0) {
                        for (var i = 0; i < m; i++) {
                            if (i != j) {
                                var scale = -cpy[i][j] / cpy[j][j];
                                for (k = j; k < n; k++) {
                                    cpy[i][k] = cpy[i][k] + scale * cpy[j][k];
                                }
                                for (k = 0; k < n; k++) {
                                    mtx[i][k] = mtx[i][k] + scale * mtx[j][k];
                                }
                            }
                        }
                    }
                }
                for (i = 0; i < m; i++) {
                    for (j = 0; j < n; j++) {
                        mtx[i][j] = mtx[i][j] / cpy[i][i];
                    }
                }
                // Calculates the determinant of the matrix.
                var det = 1;
                for (i = 0; i < m; i++) {
                    det = det * cpy[i][i];
                }
                if (det == 0) {
                    throw new Error('The matrix is singular, in the expression inv(' + core.toString(obj) + ').');
                }
            } else {
                throw new Error('The argument for function inv() must be a matrix, in the expression inv(' + core.toString(obj) + ').');
            }
            return mtx;
        }

        /**
         * Join the elements of an array using the indicated separator.
         * @param {array}    mtx - The array to join elements.
         * @param {string}   char - The separator character.
         * @return {string}  The string containing the parts of the array.
         */
        this.join = function(mtx, char) {
            return mtx.split(char);
        }

        /**
         * Returns the last position of one string in another.
         * @param {string}   str - The string containing the other one.
         * @param {string}   text - Search string.
         * @return {number}  The position of last occurrence of string in the other.
         */
        this.lastIndexOf = function(str, text) {
            return str.lastIndexOf(text);
        }

        /**
         * Returns the size of an object.
         * @param {string}   obj - Object to be measured.
         * @return {number}  Object size.
         */
        this.length = function(obj) {
            return obj.length;
        }

        /**
         * Creates a two-dimensional array (matrix).
         * @param {object}  obj - Object to fill the matrix cells.
         * @param {number}  rows - Number of rows in the matrix.
         * @param {number}  columns - Number of columns in the matrix.
         * @return {array}  A (rows x columns) matrix.
         */
        this.matrix = function(obj, rows, columns) {
            var mtx = [];
            if (rows > 1) {
                for (var i = 0; i < rows; i++) {
                    var row = [];
                    for (var j = 0; j < columns; j++) {
                        row.push(obj);
                    }
                    mtx.push(row);
                }
            } else {
                var row = [];
                for (var j = 0; j < columns; j++) {
                    row.push(obj);
                }
                mtx = row;
            }
            return mtx;
        }

        /**
         * Creates a new instance of an object.
         * @param {object}   obj - The object that will be used as a template.
         * @param {object}   properties - The object properties.
         * @return {number}  A new instance of an object.
         */
        this.new = function(obj) {
            if (typeof properties == 'undefined') {
                var newObject = Object.create(obj);
            } else {
                var newObject = Object.create(obj, properties);
            }
            return newObject;
        }

        /**
         * Creates a unitary matrix.
         * @param {number}  rows - Number of rows in the matrix.
         * @param {number}  columns - Number of columns in the matrix.
         * @return {array}  A (rows x columns) matrix.
         */
        this.one = function(rows, columns) {
            return core.matrix(1, rows, columns);
        }

        /**
         * Opens or creates a database.
         * @param {string}   name - Database name.
         * @param {string}   version - Scheme version.
         * @param {string}   displayName -  The display name of the database.
         * @param {string}   estimatedSize - Estimated maximum size.
         * @return {object}  Reference to the open or created database.
         */
        this.openSQLDatabase = function(name, version, displayName, estimatedSize) {
            var db;

            try {
                db = openDatabase(name, version, displayName, estimatedSize);
            } catch (e) {
                system.log(e.message);
                throw new Error(e.message);
            }

            return db;
        }

        /**
         * Removes an object from the end of an array.
         * @param {array}   mtx - The array to join elements.
         * @param {object}  obj - The separator character.
         * @return {array}  The array with the object removed.
         */
        this.pop = function(mtx, obj) {
            return mtx.pop(obj);
        }

        /**
         * Insert an object at the end of an array.
         * @param {array}   mtx - The array to join elements.
         * @param {object}  obj - The separator character.
         * @return {array}  The array with the added object.
         */
        this.push = function(mtx, obj) {
            return mtx.push(obj);
        }

        /**
         * Returns the real part of a complex number.
         * @param {object}   obj - The complex number.
         * @return {number}  The real part of a complex number.
         */
        this.real = function(obj) {
            var num;
            if (typeof obj == 'object') {
                if ('imaginary' in obj) {
                    num = obj.real;
                } else {
                    throw new Error('The object is not a complex number, in the expression real(' + core.toString(obj) + ').');
                }
            } else {
                throw new Error('The object is not a complex number, in the expression real(' + core.toString(obj) + ').');
            }
            return num;
        }

        /**
         * Create a RegExp object to compare an expression with a specified pattern (regular expression).
         * @param {string}   pattern - The regular expression.
         * @param {string}   flags - Indicates the marks that can be added.
         * @return {object}  A RegExp object.
         */
        this.regExp = function(pattern, flags) {
            var regexp = new RegExp(pattern, flags);
            return regexp;
        }

        /**
         * Returns a new string with a specified number of copies of the string.
         * @param {object}   str - The object to convert to do string.
         * @param {number}   count - Number of copies.
         * @return {string}  A new string with a specified number of copies of the string.
         */
        this.repeat = function(str, count) {
            return str.repeat(count);
        }

        /**
         * Replaces one character string with another in a string.
         * @param {string}   str - The string containing the other one.
         * @param {string}   string1 - The string to search for.
         * @param {string}   string2 - The replacement string.
         * @return {string}  A new string.
         */
        this.replace = function(str, string1, string2) {
            return str.replace(string1, string2);
        }

        /**
         * Searches a string for a specified value.
         * @param {string}   str - The string containing the other one.
         * @param {string}   text - Search string.
         * @return {number}  The position of the match.
         */
        this.search = function(str, text) {
            return str.search(text);
        }

        /**
         * Insert an object at the beginning of an array.
         * @param {array}   mtx - The array to join elements.
         * @param {object}  obj - The separator character.
         * @return {array}  The array with the added object.
         */
        this.shift = function(mtx, obj) {
            return mtx.shift(obj);
        }

        /**
         * Return a part of a string or array.
         * @param {string}   obj - The string or array containing the other one.
         * @param {number}   start - The start position.
         * @param {number}   end - The final position.
         * @return {string}  The selected part of the string or array.
         */
        this.slice = function(obj, start, end) {
            if (typeof end != 'undefined') {
                return obj.slice(start, end);
            } else {
                return obj.slice(start);
            }
        }

        /**
         * Removes or replaces an object from the specified position in an array.
         * @param {array}   mtx - The array to remove elements.
         * @param {number}  pos - Position from which objects will be removed.
         * @param {number}  count - Number of objects to remove.
         * @param {object}  obj - Object to be inserted in the specified location.
         * @return {array}  The array with the objects removed.
         */
        this.splice = function(mtx, pos, count, obj) {
            if (typeof obj != 'undefined') {
                return mtx.splice(pos, count, obj);
            } else {
                return mtx.splice(pos, count);
            }
        }

        /**
         * Convert a string to an array, using the character indicated as a separator.
         * @param {string}   str - The string to slit.
         * @param {string}   chars - The separator characters.
         * @return {array}   The array containing the parts of the string.
         */
        this.split = function(str, chars) {
            if (typeof chars == 'undefined') {
                chars = ' ';
            }
            var firstChar = chars[0];
            for (var i = 1; i < chars.length; i++) {
                str = str.split(chars[i]).join(firstChar);
            }
            str = str.split(firstChar);
            return str;
        }

        /**
         * Convert a CSV record to an array, using the character indicated as the column separator.
         * @param {string}   str - The string to slit.
         * @param {string}   separator - The separator characters.
         * @param {boolean}  allowRepeatChar - The separator character can be repeated (for formatting).
         * @param {boolean}  doEval - Run core.eval before adding the column to the record.
         * @return {array}   The array containing the parts of the CSV or NULL if the CSV record is not well formed.
         */
        this.splitCSV = function(str, separator, allowRepeatChar, doEval) {
            var record = [];
            var column = '';
            var previous = '';
            var insideAString = false;
            var openParentheses = false;
            var closedParentheses = false;
            var openBrackets = false;
            var closedBrackets = false;
            var openBraces = false;
            var closedBraces = false;
            var i = 0;
            var j = 0;
            if (typeof separator == 'undefined') {
                separator = ',';
            }
            if (typeof allowRepeatChar == 'undefined') {
                var allowRepeatChar = false;
            }
            if (typeof doEval == 'undefined') {
                var doEval = false;
            }
            while (j < str.length) {
                c = str[j];
                if (insideAString) {
                    if (((c == '"') || (c == '\'')) && (previous != '\\')) {
                        insideAString = !insideAString;
                        if (doEval) {
                            column += '"';
                        }
                    } else {
                        column += c;
                    }
                } else {
                    if (((c == '"') || (c == '\'')) && (previous != '\\')) {
                        insideAString = !insideAString;
                        if (doEval) {
                            column += '"';
                        }
                    } else {
                        if (c == '(') {
                            openParentheses++;
                        } else if (c == ')') {
                            closedParentheses++;
                        }
                        if (c == '[') {
                            openBrackets++;
                        } else if (c == ']') {
                            closedBrackets++;
                        }
                        if (c == '{') {
                            openBraces++;
                        } else if (c == '}') {
                            closedBraces++;
                        }

                        if (openParentheses > 0) {
                            if (openParentheses != closedParentheses) {
                                column += c;
                            } else {
                                openParentheses = 0;
                                closedParentheses = 0;
                            }
                        }
                        if (openBrackets > 0) {
                            if (openBrackets != closedBrackets) {
                                column += c;
                            } else {
                                openBrackets = 0;
                                closedBrackets = 0;
                            }
                        }
                        if (openBraces > 0) {
                            if (openBraces != closedBraces) {
                                column += c;
                            } else {
                                openBraces = 0;
                                closedBraces = 0;
                            }
                        }

                        if ((openParentheses == 0) && (openBrackets == 0) && (openBraces == 0)) {
                            if (separator.includes(c)) {
                                if (allowRepeatChar) {
                                    while (separator.includes(str[j])) {
                                        if (j < str.length) {
                                            j++;
                                        }
                                        if (j == str.length) {
                                            break;
                                        }
                                    }
                                    j--;
                                }
                                if (doEval) {
                                    record[i] = core.eval(column);
                                } else {
                                    record[i] = column;
                                }
                                column = '';
                                i++;
                            } else {
                                column += c;
                            }
                        }
                    }
                }
                previous = c;
                j++;
            }
            if (doEval) {
                record[i] = core.eval(column);
            } else {
                record[i] = column;
            }
            return record;
        }

        /**
         * Return a part of a string.
         * @param {string}   str - The string containing the other one.
         * @param {number}   start - The start position.
         * @param {number}   size - The the size of the slice.
         * @return {string}  The selected part of the string.
         */
        this.substr = function(str, start, size) {
            return str.substr(start, size);
        }

        /**
         * Tests a script, checking if the result of its execution corresponds to the expected result, considering the specified tolerance.
         * @param {string}    _script - The script to be evaluated.
         * @param {number}    _times - Number of times the test must be repeated.
         * @param {number}    _value - Expected value.
         * @param {number}    _tolerance - Tolerance.
         * @param {string}    _catchScript - Script to be evaluated if the test fails.
         * @return {boolean}  True if the test was successful or false, otherwise.
         */
        this.testScript = function(_script, _times, _value, _tolerance, _catchScript) {
            if (typeof _times == 'undefined') {
                _times = 1;
            }
            if (typeof _tolerance == 'undefined') {
                _tolerance = 0;
            }
            var _successfulTest = true;
            var _i = 0;
            while (_i < _times) {
                this.testResult.obtained = eval(_script);
                if (typeof _value != 'undefined') {
                    if (_tolerance > 0) {
                        if ((typeof this.testResult.obtained == 'number') && (typeof _value == 'number')) {
                            if (!((this.testResult.obtained >= (_value - _tolerance)) && (this.testResult.obtained <= (_value + _tolerance)))) {
                                this.testResult.expected = _value;
                                _successfulTest = false;
                                if (typeof _catchScript != 'undefined') {
                                    eval(_catchScript);
                                }
                                break;
                            }
                        } else {
                            throw new Error('The test statement only supports tolerance with numeric values.');
                        }
                    } else {
                        if (!core.equal(this.testResult.obtained, _value)) {
                            this.testResult.expected = _value;
                            _successfulTest = false;
                            if (typeof _catchScript != 'undefined') {
                                eval(_catchScript);
                            }
                            break;
                        }
                    }
                }
                _i++;
            }
            return _successfulTest;
        }

        /**
         * Converts a string to lower case.
         * @param {string}   text - The string to convert.
         * @return {string}  A new string.
         */
        this.toLowerCase = function(text) {
            return text.toLowerCase();
        }

        /**
         * Converts a string representing a number to a binary number.
         * @param {string}   text - The string representing a number.
         * @return {number}  The string coverted to number.
         */
        this.toNumber = function(text) {
            var num;
            if (core.type(text) == 'string') {
                if (text.includes('i')) {
                    var compiler = new MaiaCompiler();
                    num = JSON.parse(compiler.parseComplexNumber(text));
                } else {
                    num = Number(text);
                }
            } else if (core.type(text) == 'number') {
                num = text;
            }
            return num;
        }

        /**
         * Converts an objecto to string.
         * @param {object}   obj - The object to convert to do string.
         * @param {number}   base - Numerical base for conversion.
         * @return {string}  The object coverted for string.
         */
        this.toString = function(obj, base) {
            var str = '';
            if (typeof obj == 'object') {
                if ('imaginary' in obj) {
                    var signal = Math.sign(obj.imaginary) > 0 ? '+' : '-';
                    str = (obj.real).toString() + signal + Math.abs(obj.imaginary).toString() + '*i';
                } else {
                    str = JSON.stringify(obj);
                }
            } else {
                if (typeof base != 'undefined') {
                    str = obj.toString(base);
                } else {
                    str = obj.toString();
                }
            }
            return str;
        }

        /**
         * Converts a string to uppercase.
         * @param {string}   text - The string to convert.
         * @return {string}  A new string.
         */
        this.toUpperCase = function(text) {
            return text.toUpperCase();
        }

        /**
         * Removes characters from the beginning and end of a string.
         * @param {string}   str - The string to be trimmed
         * @param {string}   chars - The characters to remove.
         * @return {string}  A new string.
         */
        this.trim = function(str, chars) {
            if (typeof chars == 'undefined') {
                return str.trim();
            }
            if (chars == ']') {
                var chars = '\\]';
            }
            if (chars == '\\') {
                var chars = '\\\\';
            }
            return str.replace(new RegExp('^[' + chars + ']+|[' + chars + ']+$', 'g'), '');
        }

        /**
         * Removes characters from the beginning and end of a string.
         * @param {string}   str - The string to be trimmed
         * @param {string}   chars - The characters to remove.
         * @return {string}  A new string.
         */
        this.trimLeft = function(str, chars) {
            if (typeof chars == 'undefined') {
                return str.trim();
            }
            if (chars == ']') {
                var chars = '\\]';
            }
            if (chars == '\\') {
                var chars = '\\\\';
            }
            return str.replace(new RegExp('^[' + chars + ']+', 'g'), '');
        }

        /**
         * Removes characters from the beginning and end of a string.
         * @param {string}   str - The string to be trimmed
         * @param {string}   chars - The characters to remove.
         * @return {string}  A new string.
         */
        this.trimRight = function(str, chars) {
            if (typeof chars == 'undefined') {
                return str.trim();
            }
            if (chars == ']') {
                var chars = '\\]';
            }
            if (chars == '\\') {
                var chars = '\\\\';
            }
            return str.replace(new RegExp('[' + chars + ']+$', 'g'), '');
        }

        /**
         * Returns the class of a MaiaScript object.
         * @param {object}   obj - A MaiaScript object .
         * @return {string}  The class of a MaiaScript object.
         */
        this.type = function(obj) {
            var classType;
            if (typeof obj == 'boolean') {
                classType = 'boolean';
            } else if (typeof obj == 'function') {
                classType = 'function';
            } else if (typeof obj == 'number') {
                classType = 'number';
            } else if (typeof obj == 'string') {
                classType = 'string';
            } else if (typeof obj == 'object') {
                if (Array.isArray(obj)) {
                    classType = 'matrix';
                } else {
                    if ('imaginary' in obj) {
                        classType = 'complex';
                    } else {
                        classType = 'object';
                    }
                }
            } else if (typeof obj == 'undefined') {
                classType = 'undefined';
            }
            return classType;
        }

        /**
         * Removes an object from the beginning of an array.
         * @param {array}   mtx - The array to join elements.
         * @param {object}  obj - The separator character.
         * @return {array}  The array with the object removed.
         */
        this.unshift = function(mtx, obj) {
            return mtx.unshift(obj);
        }

        /**
         * Creates a zero matrix.
         * @param {number}  rows - Number of rows in the matrix.
         * @param {number}  columns - Number of columns in the matrix.
         * @return {array}  A (rows x columns) matrix.
         */
        this.zero = function(rows, columns) {
            return core.matrix(0, rows, columns);
        }

        /*
         * The following functions are used internally by the MaiaScript compiler.
         */

        /**
         * Performs the logical OR operation between two objects.
         * @param {object}   left - The left operand.
         * @param {object}   right - The right operand.
         * @return {string}  An string represening the result of the operation.
         */
        this.logicalOR = function(left, right) {
            return left || right;
        }

        /**
         * Performs the logical XOR operation between two objects.
         * @param {object}   left - The left operand.
         * @param {object}   right - The right operand.
         * @return {string}  An string represening the result of the operation.
         */
        this.logicalXOR = function(left, right) {
            return left ? !right : right;
        }

        /**
         * Performs the logical AND operation between two objects.
         * @param {object}   left - The left operand.
         * @param {object}   right - The right operand.
         * @return {string}  An string represening the result of the operation.
         */
        this.logicalAND = function(left, right) {
            return left && right;
        }

        /**
         * Performs the binary OR operation between two objects.
         * @param {object}   left - The left operand.
         * @param {object}   right - The right operand.
         * @return {string}  An string represening the result of the operation.
         */
        this.bitwiseOR = function(left, right) {
            return left | right;
        }

        /**
         * Performs the binary XOR operation between two objects.
         * @param {object}   left - The left operand.
         * @param {object}   right - The right operand.
         * @return {string}  An string represening the result of the operation.
         */
        this.bitwiseXOR = function(left, right) {
            return left ^ right;
        }

        /**
         * Performs the binary AND operation between two objects.
         * @param {object}   left - The left operand.
         * @param {object}   right - The right operand.
         * @return {string}  An string represening the result of the operation.
         */
        this.bitwiseAND = function(left, right) {
            return left & right;
        }

        /**
         * Returns TRUE if two objects are equal.
         * @param {object}   left - The left operand.
         * @param {object}   right - The right operand.
         * @return {string}  An string represening the result of the operation.
         */
        this.equal = function(left, right) {
            var res;

            Array.prototype.equals = function(array) {
                if (!array) {
                    return false;
                }
                if (this.length != array.length) {
                    return false;
                }
                for (var i = 0, l = this.length; i < l; i++) {
                    if (this[i] instanceof Array && array[i] instanceof Array) {
                        if (!this[i].equals(array[i])) {
                            return false;
                        }
                    } else if (this[i] != array[i]) {
                        return false;
                    }
                }
                return true;
            }
            Object.defineProperty(Array.prototype, "equals", {
                enumerable: false
            });

            isEquivalent = function(a, b) {
                var aProperties = Object.getOwnPropertyNames(a);
                var aProperties = Object.getOwnPropertyNames(b);
                if (aProperties.length != aProperties.length) {
                    return false;
                }
                for (var i = 0; i < aProperties.length; i++) {
                    var propertiesName = aProperties[i];
                    if (a[propertiesName] !== b[propertiesName]) {
                        return false;
                    }
                }
                return true;
            }

            if (Array.isArray(left) && Array.isArray(right)) {
                res = left.equals(right);
            } else {
                if ((typeof left == 'object') && (typeof right == 'object')) {
                    res = isEquivalent(left, right);
                } else {
                    res = left == right;
                }
            }
            return res;
        }

        /**
         * Returns TRUE if two objects are different.
         * @param {object}   left - The left operand.
         * @param {object}   right - The right operand.
         * @return {string}  An string represening the result of the operation.
         */
        this.different = function(left, right) {
            return left != right;
        }

        /**
         * Returns TRUE if the object on the left is smaller than the object on the right.
         * @param {object}   left - The left operand.
         * @param {object}   right - The right operand.
         * @return {string}  An string represening the result of the operation.
         */
        this.LT = function(left, right) {
            return left < right;
        }

        /**
         * Returns TRUE if the object on the left is less than or equal to the object on the right.
         * @param {object}   left - The left operand.
         * @param {object}   right - The right operand.
         * @return {string}  An string represening the result of the operation.
         */
        this.LE = function(left, right) {
            return left <= right;
        }

        /**
         * Returns TRUE if the object on the left is greater than or equal to the object on the right.
         * @param {object}   left - The left operand.
         * @param {object}   right - The right operand.
         * @return {string}  An string represening the result of the operation.
         */
        this.GE = function(left, right) {
            return left >= right;
        }

        /**
         * Returns TRUE if the object on the left is greater than the object on the right.
         * @param {object}   left - The left operand.
         * @param {object}   right - The right operand.
         * @return {string}  An string represening the result of the operation.
         */
        this.GT = function(left, right) {
            return left > right;
        }

        /**
         * Performs a left shift operation.
         * @param {object}   left - The left operand.
         * @param {object}   right - The right operand.
         * @return {string}  An string represening the result of the operation.
         */
        this.leftShift = function(left, right) {
            return left << right;
        }

        /**
         * Performs a left shift operation.
         * @param {object}   left - The left operand.
         * @param {object}   right - The right operand.
         * @return {string}  An string represening the result of the operation.
         */
        this.rightShift = function(left, right) {
            return left >> right;
        }

        /**
         * Add two objects.
         * @param {object}   left - The left operand.
         * @param {object}   right - The right operand.
         * @return {string}  An string represening the result of the operation.
         */
        this.add = function(left, right) {
            var res;
            if (core.type(left) == 'complex') {
                if (core.type(right) == 'complex') {
                    var real = core.toNumber(left.real) + core.toNumber(right.real);
                    var img = core.toNumber(left.imaginary) + core.toNumber(right.imaginary);
                    res = core.complex(real, img);
                } else if (core.type(right) == 'number') {
                    var real = core.toNumber(left.real) + core.toNumber(right);
                    var img = core.toNumber(left.imaginary);
                    res = core.complex(real, img);
                } else {
                    throw new Error('Invalid operand for operator "+", in the expression ' + core.toString(left) + ' + ' + core.toString(right) + '.');
                }
            } else if (core.type(left) == 'number') {
                if (core.type(right) == 'complex') {
                    var real = core.toNumber(left) + core.toNumber(right.real);
                    var img = core.toNumber(right.imaginary);
                    res = core.complex(real, img);
                } else if (core.type(right) == 'number') {
                    res = left + right;
                } else if (core.type(right) == 'string') {
                    res = core.toString(left) + right;
                } else {
                    throw new Error('Invalid operand for operator "+", in the expression ' + core.toString(left) + ' + ' + core.toString(right) + '.');
                }
            } else if (core.type(left) == 'matrix') {
                if (core.type(right) == 'matrix') {
                    res = [];
                    var dimLeft = core.dim(left);
                    var dimRight = core.dim(right);
                    if ((dimLeft.length > 1) && (dimRight.length > 1)) {
                        if ((dimLeft[0] == dimRight[0]) && (dimLeft[1] == dimRight[1])) {
                            var rows = dimLeft[0];
                            var columns = dimLeft[1];
                            for (var i = 0; i < rows; i++) {
                                var row = [];
                                for (var j = 0; j < columns; j++) {
                                    row.push(left[i][j] + right[i][j]);
                                }
                                res.push(row);
                            }
                        } else {
                            throw new Error('Operand invalid for operator "+", in the expression ' + core.toString(left) + ' + ' + core.toString(right) + '. The matrices must have the same dimensions.');
                        }
                    } else {
                        if (dimLeft[0] == dimRight[0]) {
                            var columns = dimLeft[0];
                            var row = [];
                            for (var i = 0; i < columns; i++) {
                                row.push(left[i] + right[i]);
                            }
                            res = row;
                        } else {
                            throw new Error('Operand invalid for operator "+", in the expression ' + core.toString(left) + ' + ' + core.toString(right) + '. The matrices must have the same dimensions.');
                        }
                    }
                } else {
                    throw new Error('Invalid operand for operator "+", in the expression ' + core.toString(left) + ' + ' + core.toString(right) + '.');
                }
            } else if (core.type(left) == 'string') {
                if (core.type(right) == 'number') {
                    res = left + core.toString(right);
                } else if (core.type(right) == 'string') {
                        res = left + right;
                } else {
                    throw new Error('Invalid operand for operator "+", in the expression ' + core.toString(left) + ' + ' + core.toString(right) + '.');
                }
            } else {
                res = left + right;
            }
            return res;
        }

        /**
         * Subtracts two objects.
         * @param {object}   left - The left operand.
         * @param {object}   right - The right operand.
         * @return {string}  An string represening the result of the operation.
         */
        this.sub = function(left, right) {
            var res;
            if (core.type(left) == 'complex') {
                if (core.type(right) == 'complex') {
                    var real = core.toNumber(left.real) - core.toNumber(right.real);
                    var img = core.toNumber(left.imaginary) - core.toNumber(right.imaginary);
                    res = core.complex(real, img);
                } else if (core.type(right) == 'number') {
                    var real = core.toNumber(left.real) - core.toNumber(right);
                    var img = core.toNumber(left.imaginary);
                    res = core.complex(real, img);
                } else {
                    throw new Error('Invalid operand for operator "-", in the expression ' + core.toString(left) + ' - ' + core.toString(right) + '.');
                }
            } else if (core.type(left) == 'number') {
                if (core.type(right) == 'complex') {
                    var real = core.toNumber(left) - core.toNumber(right.real);
                    var img = core.toNumber(right.imaginary);
                    res = core.complex(real, img);
                } else if (core.type(right) == 'number') {
                    res = left - right;
                } else {
                    throw new Error('Invalid operand for operator "-", in the expression ' + core.toString(left) + ' - ' + core.toString(right) + '.');
                }
            } else if (core.type(left) == 'matrix') {
                if (core.type(right) == 'matrix') {
                    res = [];
                    var dimLeft = core.dim(left);
                    var dimRight = core.dim(right);
                    if ((dimLeft.length > 1) && (dimRight.length > 1)) {
                        if ((dimLeft[0] == dimRight[0]) && (dimLeft[1] == dimRight[1])) {
                            var rows = dimLeft[0];
                            var columns = dimLeft[1];
                            for (var i = 0; i < rows; i++) {
                                var row = [];
                                for (var j = 0; j < columns; j++) {
                                    row.push(left[i][j] - right[i][j]);
                                }
                                res.push(row);
                            }
                        } else {
                            throw new Error('Operand invalid for operator "-", in the expression ' + core.toString(left) + ' - ' + core.toString(right) + '. The matrices must have the same dimensions.');
                        }
                    } else {
                        if (dimLeft[0] == dimRight[0]) {
                            var columns = dimLeft[0];
                            var row = [];
                            for (var i = 0; i < columns; i++) {
                                row.push(left[i] - right[i]);
                            }
                            res = row;
                        } else {
                            throw new Error('Operand invalid for operator "-", in the expression ' + core.toString(left) + ' - ' + core.toString(right) + '. The matrices must have the same dimensions.');
                        }
                    }
                } else {
                    throw new Error('Invalid operand for operator "-", in the expression ' + core.toString(left) + ' - ' + core.toString(right) + '.');
                }
            } else {
                res = left - right;
            }
            return res;
        }

        /**
         * Performs a power operation between two objects.
         * @param {object}   left - The left operand.
         * @param {object}   right - The right operand.
         * @return {string}  An string represening the result of the operation.
         */
        this.power = function(left, right) {
            // r=abs(a+b*i)=sqrt(a*a+b*b)
            // t=arg(a+b*i)=atan(b/a)
            // pow(a+b*i,n)=pow(r,n)*cos(n*t)+i*pow(r,n)*sin(n*t)
            var res;
            if (core.type(left) == 'complex') {
                if (core.type(right) == 'number') {
                    var r = Math.sqrt(core.toNumber(left.real) * core.toNumber(left.real) + core.toNumber(left.imaginary) * core.toNumber(left.imaginary));
                    var a = Math.asin(core.toNumber(left.imaginary) / r);
                    var real = Math.pow(r, right) * Math.cos(a * right);
                    var img = Math.pow(r, right) * Math.sin(a * right);
                    res = core.complex(real, img);
                } else {
                    throw new Error('Invalid operand for operator "^", in the expression ' + core.toString(left) + ' ^ ' + core.toString(right) + '.');
                }
            } else if (core.type(left) == 'matrix') {
                if (core.type(right) == 'number') {
                    var dimLeft = core.dim(left);
                    if (right == -1) {
                        res = core.inv(left);
                    } else if (right == 0) {
                        res = core.one(dimLeft[0], dimLeft[1]);
                    } else if (right == 1) {
                        res = left;
                    } else if (right > 1) {
                        res = 1;
                        for (var i = 0; i < right; i++) {
                            res = core.mul(res, left);
                        }
                    }
                } else {
                    throw new Error('Invalid operand for operator "^", in the expression ' + core.toString(left) + ' ^ ' + core.toString(right) + '.');
                }
            } else {
                res = Math.pow(left, right);
            }
            return res;
        }

        /**
         * Multiplies two objects.
         * @param {object}   left - The left operand.
         * @param {object}   right - The right operand.
         * @return {string}  An string represening the result of the operation.
         */
        this.mul = function(left, right) {
            var res;
            if (core.type(left) == 'complex') {
                if (core.type(right) == 'complex') {
                    var real = core.toNumber(left.real) * core.toNumber(right.real) - core.toNumber(left.imaginary) * core.toNumber(right.imaginary);
                    var img = core.toNumber(left.real) * core.toNumber(right.imaginary) + core.toNumber(left.imaginary) * core.toNumber(right.real);
                    res = core.complex(real, img);
                } else if (core.type(right) == 'number') {
                    var real = core.toNumber(left.real) * core.toNumber(right);
                    var img = core.toNumber(left.imaginary);
                    res = core.complex(real, img);
                } else {
                    throw new Error('Invalid operand for operator "*", in the expression ' + core.toString(left) + ' * ' + core.toString(right) + '.');
                }
            } else if (core.type(left) == 'number') {
                if (core.type(right) == 'complex') {
                    var real = core.toNumber(left) * core.toNumber(right.real);
                    var img = core.toNumber(right.imaginary);
                    res = core.complex(real, img);
                } else if (core.type(right) == 'matrix') {
                    var dimRight = core.dim(right);
                    res = core.matrix(0, dimRight[0], dimRight[1]);
                    for (var i = 0; i < dimRight[0]; i++) {
                        for (var j = 0; j < dimRight[1]; j++) {
                            res[i][j] = left * right[i][j];
                        }
                    }
                } else if (core.type(right) == 'number') {
                    res = left * right;
                } else {
                    throw new Error('Invalid operand for operator "*", in the expression ' + core.toString(left) + ' * ' + core.toString(right) + '.');
                }
            } else if (core.type(left) == 'matrix') {
                if (core.type(right) == 'matrix') {
                    var dimLeft = core.dim(left);
                    var dimRight = core.dim(right);
                    res = core.matrix(0, dimLeft[0], dimRight[1]);
                    if (dimLeft[1] == dimRight[0]) {
                        for (var i = 0; i < dimLeft[0]; i++) {
                            for (var j = 0; j < dimRight[1]; j++) {
                                for (var k = 0; k < dimRight[0]; k++) {
                                    res[i][j] = res[i][j] + left[i][k] * right[k][j];
                                }
                            }
                        }
                    } else {
                        throw new Error('Operand invalid for operator "*", in the expression ' + core.toString(left) + ' * ' + core.toString(right) + '. The matrices must have compatible dimensions.');
                    }
                } else if (core.type(right) == 'number') {
                    dimLeft = core.dim(left);
                    res = core.matrix(0, dimLeft[0], dimLeft[1]);
                    for (var i = 0; i < dimLeft[0]; i++) {
                        for (var j = 0; j < dimLeft[1]; j++) {
                            res[i][j] = left[i][j] * right;
                        }
                    }
                } else {
                    throw new Error('Invalid operand for operator "*", in the expression ' + core.toString(left) + ' * ' + core.toString(right) + '.');
                }
            } else {
                res = left * right;
            }
            return res;
        }

        /**
         * Divide two objects.
         * @param {object}   left - The left operand.
         * @param {object}   right - The right operand.
         * @return {string}  An string represening the result of the operation.
         */
        this.div = function(left, right) {
            // (a+b*i)/(c+d*i)=(a*c+b*d)/(c*c+d*d)+i*(b*c-a*d)/(c*c+d*d)
            var res;
            if (core.type(left) == 'complex') {
                if (core.type(right) == 'complex') {
                    var real = (core.toNumber(left.real) * core.toNumber(right.real) + core.toNumber(left.imaginary) * core.toNumber(right.imaginary)) / (core.toNumber(right.real) * core.toNumber(right.real) + core.toNumber(right.imaginary) * core.toNumber(right.imaginary));
                    var img = (core.toNumber(left.imaginary) * core.toNumber(right.real) - core.toNumber(left.real) * core.toNumber(right.imaginary)) / (core.toNumber(right.real) * core.toNumber(right.real) + core.toNumber(right.imaginary) * core.toNumber(right.imaginary));
                    res = core.complex(real, img);
                } else if (core.type(right) == 'number') {
                    var real = core.toNumber(left.real) / core.toNumber(right);
                    var img = core.toNumber(left.imaginary);
                    res = core.complex(real, img);
                } else {
                    throw new Error('Invalid operand for operator "/", in the expression ' + core.toString(left) + ' / ' + core.toString(right) + '.');
                }
            } else if (core.type(left) == 'number') {
                if (core.type(right) == 'complex') {
                    var real = core.toNumber(left) / core.toNumber(right.real);
                    var img = core.toNumber(right.imaginary);
                    res = core.complex(real, img);
                } else if (core.type(right) == 'number') {
                    res = left / right;
                } else {
                    throw new Error('Invalid operand for operator "/", in the expression ' + core.toString(left) + ' / ' + core.toString(right) + '.');
                }
            } else if (core.type(left) == 'matrix') {
                if (core.type(right) == 'matrix') {
                    var dimLeft = core.dim(left);
                    var dimRight = core.dim(right);
                    if (dimLeft[0] == dimRight[1]) {
                        res = core.mul(core.inv(right), left);
                    } else {
                        throw new Error('Operand invalid for operator "*", in the expression ' + core.toString(left) + ' * ' + core.toString(right) + '. The matrices must have compatible dimensions.');
                    }
                } else if (core.type(right) == 'number') {
                    dimLeft = core.dim(left);
                    res = core.matrix(0, dimLeft[0], dimLeft[1]);
                    for (var i = 0; i < dimLeft[0]; i++) {
                        for (var j = 0; j < dimLeft[1]; j++) {
                            res[i][j] = left[i][j] * right;
                        }
                    }
                } else {
                    throw new Error('Invalid operand for operator "*", in the expression ' + core.toString(left) + ' * ' + core.toString(right) + '.');
                }
            } else {
                res = left / right;
            }
            return res;
        }

        /**
         * Calculates the rest of the division between two objects.
         * @param {object}   left - The left operand.
         * @param {object}   right - The right operand.
         * @return {string}  An string represening the result of the operation.
         */
        this.mod = function(left, right) {
            return left % right;
        }

        /**
         * Performs a binary NOT operation.
         * @param {object}   right - The right operand.
         * @return {string}  An string represening the result of the operation.
         */
        this.bitwiseNot = function(right) {
            return ~right;
        }

        /**
         * Performs a logical NOT operation.
         * @param {object}   right - The right operand.
         * @return {string}  An string represening the result of the operation.
         */
        this.logicalNot = function(right) {
            return !right;
        }
    }

    core = new Core();