UNPKG

nerdamer-ts

Version:

javascript light-weight symbolic math expression evaluator

607 lines 22.3 kB
"use strict"; // noinspection JSUnusedGlobalSymbols var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); /* * Author : Martin Donk * Website : http://www.nerdamer.com * Email : martin.r.donk@gmail.com * Source : https://github.com/jiggzson/nerdamer */ const Expression_1 = require("./Parser/Expression"); const Settings_1 = require("./Settings"); const Core_1 = require("./Core/Core"); const decimal_js_1 = __importDefault(require("decimal.js")); const Utils = __importStar(require("./Core/Utils")); const LaTeX_1 = require("./LaTeX/LaTeX"); const Errors_1 = require("./Core/Errors"); //version ====================================================================== const VERSION = '1.1.12'; // set bigInt the precision to js precision decimal_js_1.default.set({ precision: 250 }); const defaultCore = new Core_1.Core(); let parser = defaultCore.PARSER; let functionProvider = defaultCore.functionProvider; let variableDictionary = defaultCore.variableDictionary; let EXPRESSIONS = defaultCore.EXPRESSIONS; let peekers = defaultCore.peekers; let operators = defaultCore.operators; /** * * @param {string|Expression} expression The expression being parsed. * @param {object} subs An object of known values * @param {string} options A string or array containing additional options such as parsing directly to number * or expanding the expression. Use "numer" to when wanting the expression to be * evaluated. Use "expand" when wanting the expression to be expanded. * @param {number} location The index of where the expression should be stored. * @returns {Expression & Spread<[Math]>} */ function nerdamer(expression, subs, options, location) { // Initiate the numer flag let numer = false; // let variable, fn, args; // Convert any expression passed in to a string if (typeof expression !== 'string') { expression = expression.toString(); } // Is the user declaring a function? let fndec = /^([a-z_][a-z\d_]*)\(([a-z_,\s]*)\):=(.+)$/gi.exec(expression); if (fndec) { return nerdamer.setFunction(fndec[1], fndec[2].split(','), fndec[3]); } // Convert it to an array for simplicity if (!options) { options = []; } else if (!Array.isArray(options)) { options = [options.toString()]; } options.forEach(function (o) { // Turn on the numer flag if requested if (o === 'numer') { numer = true; return; } // Wrap it in a function if requested. This only holds true for // functions that take a single argument which is the expression let f = functionProvider.getFunctionDescriptor(o); // If there's a function and it takes a single argument, then wrap // the expression in it if (f && f[1] === 1) { expression = `${o}(${expression})`; } }); let e = Utils.block('PARSE2NUMBER', function () { return parser.parse(expression, subs); }, numer || Settings_1.Settings.PARSE2NUMBER); if (location) { EXPRESSIONS[location - 1] = e; } else { EXPRESSIONS.push(e); } return new Expression_1.Expression(e); } (function (nerdamer) { /** * Converts expression into rpn form * @param {string} expression * @returns {Token[]} */ function rpn(expression) { return parser.toRPN(parser.tokenize(expression)); } nerdamer.rpn = rpn; /** * Generates Converts and expression to LaTeX without evaluating expression. * @param {string} expression The expression being converted * @param {ConvertToLaTeXOptions} options * @returns {string} */ function convertToLaTeX(expression, options) { return parser.toTeX(expression, options); } nerdamer.convertToLaTeX = convertToLaTeX; /** * Attempts to import a LaTeX string. * @param {string} expression The expression being converted * @returns {string} */ function convertFromLaTeX(expression) { let txt = LaTeX_1.LaTeX.parse(parser.tokenize(expression)); return new Expression_1.Expression(parser.parse(txt)); } nerdamer.convertFromLaTeX = convertFromLaTeX; /** * Get the version of nerdamer or a loaded add-on * @param {string} addon - The add-on being checked * @returns {string} returns the version of nerdamer */ function version(addon) { if (addon) { try { return defaultCore[addon].version; } catch (e) { return "No module named " + addon + " found!"; } } return VERSION; } nerdamer.version = version; /** * Get nerdamer generated warnings * @returns {string[]} */ function getWarnings() { return Utils.WARNINGS; } nerdamer.getWarnings = getWarnings; /** * Sets a constant value which nerdamer will automatically substitute when parsing expression/equation. * Set to "delete" or "" to unset. * @param {string} constant The name of the constant to be set * @param {number|Expression|string} value The value of the constant * @returns {Nerdamer} Returns the nerdamer object */ function setConstant(constant, value) { Utils.validateName(constant); if (!variableDictionary.isReserved(constant)) { //fix for issue #127 if (value === 'delete' || value === '') { variableDictionary.deleteConstant(constant); } else { // TODO: fix check // if (isNaN(value)) { // throw new NerdamerTypeError('Constant must be a number!'); // } variableDictionary.setConstant(constant, value); } } return nerdamer; } nerdamer.setConstant = setConstant; /** * Returns the value of a previously set constant * @param {string} constant * @returns {string} */ function getConstant(constant) { return String(variableDictionary.getConstant(constant)); } nerdamer.getConstant = getConstant; /** * Sets a function which can then be called using nerdamer. * @param {string} name The name of the function * @param {string[]} params_array A list containing the parameter name of the functions * @param {string} body The body of the function * @returns {boolean} returns true if succeeded and falls on fail * @example nerdamer.setFunction('f',['x'], 'x^2+2'); */ function setFunction(name, params_array, body) { parser.setFunction(name, params_array, body); } nerdamer.setFunction = setFunction; /** * Returns the nerdamer core object. This object contains all the core functions of nerdamer and houses the parser. * @returns {Core} Exports the nerdamer core functions and objects */ function getCore() { return defaultCore; } nerdamer.getCore = getCore; /** * Returns stored expression at index. For first index use 1 not 0. * @param {number|string} expression_number * @param {boolean} asType */ function getExpression(expression_number, asType = false) { return Expression_1.Expression.getExpression(expression_number, asType); } nerdamer.getExpression = getExpression; nerdamer.getEquation = getExpression; /** * * @param {boolean} asArray The returned names are returned as an array if this is set to true; * @returns {string|string[]} */ function reserved(asArray = false) { let reserved = variableDictionary.getReserved(); if (asArray) { return reserved; } return reserved.join(', '); } nerdamer.reserved = reserved; /** * * @param {number|'all'} equation_number the number of the equation to clear. * If 'all' is supplied then all equations are cleared * @param {boolean} keep_EXPRESSIONS_fixed use true if you don't want to keep EXPRESSIONS length fixed * @returns {Nerdamer} Returns the nerdamer object */ function clear(equation_number, keep_EXPRESSIONS_fixed = false) { if (equation_number === 'all') { EXPRESSIONS.splice(0, EXPRESSIONS.length); } else if (equation_number === 'last') { EXPRESSIONS.pop(); } else if (equation_number === 'first') { EXPRESSIONS.shift(); } else { let index = !equation_number ? EXPRESSIONS.length : equation_number - 1; keep_EXPRESSIONS_fixed ? EXPRESSIONS[index] = undefined : Utils.remove(EXPRESSIONS, index); } return nerdamer; } nerdamer.clear = clear; /** * Clears all stored expressions.; * Alias for nerdamer.clear('all') */ function flush() { clear('all'); return nerdamer; } nerdamer.flush = flush; /** * * @param {boolean} asObject * @param {boolean} asLaTeX * @param {string|string[]} options * @returns {Array} */ function expressions(asObject, asLaTeX, options) { let result = asObject ? {} : []; for (let i = 0; i < EXPRESSIONS.length; i++) { let eq = asLaTeX ? LaTeX_1.LaTeX.latex(EXPRESSIONS[i], options) : Utils.text(EXPRESSIONS[i], options); asObject ? result[i + 1] = eq : result.push(eq); } return result; } nerdamer.expressions = expressions; /** * Registers a module function with nerdamer. The object needs to contain at a minimum, a name property (text), * a numargs property (int), this is -1 for variable arguments or an array containing the min and max arguments, * the visible property (bool) which allows use of this function through nerdamer, defaults to true, and a * build property containing a function which returns the function to be used. This function is also handy for * creating aliases to functions. See below how the alias D was created for the diff function). * @param {object|object[]} obj */ function register(obj) { let core = defaultCore; if (Array.isArray(obj)) { for (let i = 0; i < obj.length; i++) { if (obj) { register(obj[i]); } } } else if (obj && 'name' in obj && Settings_1.Settings.exclude.indexOf(obj.name) === -1) { //make sure all the dependencies are available if (obj.dependencies) { for (let i = 0; i < obj.dependencies.length; i++) if (!core[obj.dependencies[i]]) throw new Error(Utils.format('{0} requires {1} to be loaded!', obj.name, obj.dependencies[i])); } //if no parent object is provided then the function does not have an address and cannot be called directly let parent_obj = obj.parent, fn = obj.build.call(core); //call constructor to get function if (parent_obj) { if (!core[parent_obj]) core[obj.parent] = {}; let ref_obj = parent_obj === 'nerdamer' ? nerdamer : core[parent_obj]; //attach the function to the core ref_obj[obj.name] = fn; } if (obj.visible) { functionProvider.setFunctionDescriptor(obj.name, [fn, obj.numargs]); //make the function available } } } nerdamer.register = register; /** * Enforces rule: "must start with a letter or underscore and * can have any number of underscores, letters, and numbers thereafter." * @param {string} name The name of the symbol being checked * @param {string} type - The type of symbols that's being validated * @throws {InvalidVariableNameError} - Throws an exception on fail */ function validateName(name, type = 'variable') { return Utils.validateName(name, type); } nerdamer.validateName = validateName; /** * This method can be used to check that the variable meets variable name requirements for nerdamer. * Variable names Must start with a letter or underscore and may contains any combination of numbers, * letters, and underscores after that. * @param {string} varname The variable name being validated * @returns {boolean} validates if the profided string is a valid variable name */ function validVarName(varname) { try { Utils.validateName(varname); return !variableDictionary.isReserved(varname); } catch (e) { return false; } } nerdamer.validVarName = validVarName; /** * Array of functions currently supported by nerdamer * @returns {Array} */ function supported() { return Object.keys(functionProvider.getFunctionDescriptors()); } nerdamer.supported = supported; /** * * @returns {Number} The number equations/expressions currently loaded */ function numExpressions() { return EXPRESSIONS.length; } nerdamer.numExpressions = numExpressions; /** * Sets a known value in nerdamer. This differs from setConstant as the value can be overridden trough * the scope. See example. Set to "delete" or "" to unset * @param {string} v The known value to be set * @param {string|number|Expression} val The value for the expression to be set to. * @returns {nerdamer} Returns the nerdamer object */ function setVar(v, val) { Utils.validateName(v); //check if it's not already a constant if (variableDictionary.isConstant(v)) { (0, Errors_1.err)('Cannot set value for constant ' + v); } if (val === 'delete' || val === '') { variableDictionary.deleteVar(v); } else { let value = Utils.isSymbol(val) ? val : parser.parse(val); variableDictionary.setVar(v, value); } return nerdamer; } nerdamer.setVar = setVar; /** * Returns the value of a set variable * @param {string} v * @returns {any} */ function getVar(v) { return variableDictionary.getVar(v); } nerdamer.getVar = getVar; /** * Gets all previously set variables. * @param {string} output - output format. Can be 'object' (just returns the VARS object), 'text' or 'latex'. Default: 'text' * @param {string|string[]} options * @returns {object} Returns an object with the variables */ function getVars(output, options) { output = output || 'text'; let variables = variableDictionary.getAllVars(); switch (output) { case 'object': return variables; case 'latex': return variables.map((v) => v.latex(options)); case 'text': return variables.map((v) => v.text(options)); } return {}; } nerdamer.getVars = getVars; /** * Clear the variables from the VARS object * @returns {Object} Returns the nerdamer object */ function clearVars() { variableDictionary.clearAllVars(); return nerdamer; } nerdamer.clearVars = clearVars; /** * * @param {Function} loader * @returns {nerdamer} */ function load(loader) { loader.call(nerdamer); return nerdamer; } nerdamer.load = load; /** * Some settings within nerdamer can be changed if needed to accommodate your current needs. * @param {string} setting The setting to be changed * @param {any} value */ function set(setting, value) { // FIXME: !!! //current options: //PARSE2NUMBER, suppress_errors // if (typeof setting === 'object') // for (let x in setting) { // set(x, setting[x]); // } // // let disallowed = ['SAFE']; // if (disallowed.indexOf(setting) !== -1) // err('Cannot modify setting: ' + setting); // // if (setting === 'PRECISION') { // bigDec.set({precision: value}); // Settings.PRECISION = value; // // // Avoid that nerdamer puts out garbage after 21 decimal place // if (value > 21) { // this.set('USE_BIG', true); // } // } // else if (setting === 'USE_LN' && value === true) { // //set log as LN // Settings.LOG = 'LN'; // //set log10 as log // Settings.LOG10 = 'log'; // //point the functions in the right direction // // const logFunc = x => { // if (x.isConstant()) // return new Symbol(Math.log10(x)); // return symfunction(Settings.LOG10, [x]); // }; // // functionProvider.setFunctionDescriptor('log', [logFunc, [1, 2]]) // functionProvider.setFunctionDescriptor('LN', Settings.LOG_FNS.log); // // //remove log10 // functionProvider.removeFunctionDescriptor('log10'); // } // else { // Settings[setting] = value; // } } nerdamer.set = set; /** * Get the value of a setting * @param {type} setting * @returns {undefined} */ function get(setting) { return Settings_1.Settings[setting]; } nerdamer.get = get; function replaceFunction(name, fn, num_args) { let existing = functionProvider.getFunctionDescriptor(name); let new_num_args = typeof num_args === 'undefined' ? existing[1] : num_args; functionProvider.setFunctionDescriptor(name, [fn.call(undefined, existing[0], defaultCore), new_num_args]); } nerdamer.replaceFunction = replaceFunction; /** * Replaces nerdamer.setOperator * @param {object} operator * @param action * @param {'over' | 'under'} shift */ function setOperator(operator, action, shift) { return operators.setOperator(operator, action, shift); } nerdamer.setOperator = setOperator; /** * Gets an opererator by its symbol * @param {String} operator * @returns {OperatorDescriptor} */ function getOperator(operator) { return operators.getOperator(operator); } nerdamer.getOperator = getOperator; function aliasOperator(operator, withOperator) { return operators.aliasOperator(operator, withOperator); } nerdamer.aliasOperator = aliasOperator; /** * Generates an RPN object which can be evaluated manually. * @param {string} expression */ function tree(expression) { return parser.tree(expression); } nerdamer.tree = tree; /** * Generates the RPN for the expression using Expression.tree and then formats it to HTML. * @param expression * @param indent */ function htmlTree(expression, indent) { let tr = tree(expression); return '<div class="tree">\n' + ' <ul>\n' + ' <li>\n' + tr.toHTML(3, indent) + '\n' + ' </li>\n' + ' </ul>\n' + '</div>'; } nerdamer.htmlTree = htmlTree; function addPeeker(name, f) { if (peekers[name]) { peekers[name].push(f); } } nerdamer.addPeeker = addPeeker; function removePeeker(name, f) { Utils.remove(peekers[name], f); } nerdamer.removePeeker = removePeeker; function parse(e) { return String(e).split(';').map(function (x) { return parser.parse(x); }); } nerdamer.parse = parse; class Nerdamer { constructor(expression, subs, options) { } static withModules(...a) { return new Nerdamer(); } } nerdamer.Nerdamer = Nerdamer; /** * DEPRECATED! Added functions available immediately. * * This functions makes internal functions available externally * @param {boolean} override Override the functions when calling api if it exists * @deprecated */ function api(override = false) { } nerdamer.api = api; })(nerdamer || (nerdamer = {})); //Required<NerdamerBaseType> | CoreFunction const proxy = new Proxy(nerdamer, { get: (target, name) => { if (name in target) { return target[name]; } if (functionProvider.getFunctionDescriptor(name)) { return (...args) => { for (let i = 0; i < args.length; i++) { args[i] = parser.parse(args[i]); } return new Expression_1.Expression(Utils.block('PARSE2NUMBER', () => { return parser.callfunction(String(name), args); })); }; } throw new Error('Requested non-existent property: ' + String(name)); } }); exports.default = proxy; //# sourceMappingURL=index.js.map