nerdamer-ts
Version:
javascript light-weight symbolic math expression evaluator
607 lines • 22.3 kB
JavaScript
;
// 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