UNPKG

nerdamer-ts

Version:

javascript light-weight symbolic math expression evaluator

719 lines 28.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.LaTeX = void 0; const Settings_1 = require("../Settings"); const Utils_1 = require("../Core/Utils"); const Groups_1 = require("../Types/Groups"); const Set_1 = require("../Types/Set"); const Collection_1 = require("../Parser/Collection"); const Parser_1 = require("../Parser/Parser"); class LaTeX { static get parser() { if (!LaTeX._parser) { LaTeX._parser = LaTeX.createParser(); } return LaTeX._parser; } static createParser() { // create a parser and strip it from everything except the items that you need var keep = ['classes', 'setOperator', 'getOperators', 'getBrackets', 'tokenize', 'toRPN', 'tree', 'units']; var parser = new Parser_1.Parser(); for (var x in parser) { if (keep.indexOf(x) === -1) delete parser[x]; } // declare the operators parser.setOperator({ precedence: 8, operator: '\\', action: 'slash', prefix: true, postfix: false, leftAssoc: true, operation: function (e) { return e; // bypass the slash } }); parser.setOperator({ precedence: 8, operator: '\\,', action: 'slash_comma', prefix: true, postfix: false, leftAssoc: true, operation: function (e) { return e; // bypass the slash } }); // have braces not map to anything. We want them to be return as-is var brackets = parser.getBrackets(); brackets['{'].maps_to = undefined; return parser; } static latex(symbol, option) { // it might be an array if (symbol.clone) { symbol = symbol.clone(); // leave original as-is } if (symbol instanceof Collection_1.Collection) symbol = symbol.elements; if (Array.isArray(symbol)) { var LaTeXArray = []; for (var i = 0; i < symbol.length; i++) { var sym = symbol[i]; //This way I can generate LaTeX on an array of strings. if (!(0, Utils_1.isSymbol)(sym)) sym = (0, Parser_1.parse)(sym); LaTeXArray.push(this.latex(sym, option)); } return this.brackets(LaTeXArray.join(', '), 'square'); } else if ((0, Utils_1.isMatrix)(symbol)) { var TeX = '\\begin{pmatrix}\n'; for (var i = 0; i < symbol.elements.length; i++) { var rowTeX = [], e = symbol.elements[i]; for (var j = 0; j < e.length; j++) { rowTeX.push(this.latex(e[j], option)); } TeX += rowTeX.join(' & '); if (i < symbol.elements.length - 1) { TeX += '\\\\\n'; } } TeX += '\\end{pmatrix}'; return TeX; } else if ((0, Utils_1.isVector)(symbol)) { var TeX = '\\left['; for (var i = 0; i < symbol.elements.length; i++) { TeX += this.latex(symbol.elements[i], option) + ' ' + (i !== symbol.elements.length - 1 ? ',\\,' : ''); } TeX += '\\right]'; return TeX; } else if ((0, Set_1.isSet)(symbol)) { var TeX = '\\{'; for (var i = 0; i < symbol.elements.length; i++) { TeX += this.latex(symbol.elements[i], option) + ' ' + (i !== symbol.elements.length - 1 ? ',\\,' : ''); } TeX += '\\}'; return TeX; } symbol = symbol.clone(); var decimal = (option === 'decimal' || option === 'decimals'), power = symbol.power, invert = (0, Utils_1.isNegative)(power), negative = symbol.multiplier.lessThan(0); if (symbol.group === Groups_1.Groups.P && decimal) { return String(symbol.multiplier.toDecimal() * Math.pow(symbol.value, symbol.power.toDecimal())); } else { symbol.multiplier = symbol.multiplier.abs(); // if the user wants the result in decimal format then return it as such by placing it at the top part var m_array; if (decimal) { var m = String(symbol.multiplier.toDecimal()); // if (String(m) === '1' && !decimal) m = ''; m_array = [m, '']; } else { m_array = [symbol.multiplier.num, symbol.multiplier.den]; } // get the value as a two part array var v_array = this.value(symbol, invert, option, negative), p; // make it all positive since we know whether to push the power to the numerator or denominator already. if (invert) power.negate(); // the power is simple since it requires no additional formatting. We can get it to a // string right away. pass in true to neglect unit powers if (decimal) { p = (0, Utils_1.isSymbol)(power) ? LaTeX.latex(power, option) : String(power.toDecimal()); if (String(p) === '1') p = ''; } // get the latex representation else if ((0, Utils_1.isSymbol)(power)) p = this.latex(power, option); // get it as a fraction else p = this.formatFrac(power, true); // use this array to specify if the power is getting attached to the top or the bottom var p_array = ['', ''], // stick it to the top or the bottom. If it's negative then the power gets placed on the bottom index = invert ? 1 : 0; p_array[index] = p; // special case group Groups.P and decimal var retval = (negative ? '-' : '') + this.set(m_array, v_array, p_array, symbol.group === Groups_1.Groups.CB); return retval.replace(/\+\-/gi, '-'); } } // get the raw value of the symbol as an array static value(symbol, inverted, option, negative) { var group = symbol.group, previousGroup = symbol.previousGroup, v = ['', ''], index = inverted ? 1 : 0; /*if (group === Groups.N) // do nothing since we want to return top & bottom blank; */ if (symbol.isInfinity) { v[index] = '\\infty'; } else if (group === Groups_1.Groups.S || group === Groups_1.Groups.P || previousGroup === Groups_1.Groups.S || previousGroup === Groups_1.Groups.P || previousGroup === Groups_1.Groups.N) { var value = this.formatSubscripts(symbol.value); if (value.replace) value = value.replace(/(.+)_$/, '$1\\_'); // split it so we can check for instances of alpha as well as alpha_b var t_varray = String(value).split('_'); var greek = this.greek[t_varray[0]]; if (greek) { t_varray[0] = greek; value = t_varray.join('_'); } var symbol = this.symbols[t_varray[0]]; if (symbol) { t_varray[0] = symbol; value = t_varray.join('_'); } v[index] = value; } else if (group === Groups_1.Groups.FN || previousGroup === Groups_1.Groups.FN) { var name, input = [], fname = symbol.fname; // collect the arguments for (var i = 0; i < symbol.args.length; i++) { var arg = symbol.args[i], item; if (typeof arg === 'string') item = arg; else { item = this.latex(arg, option); } input.push(item); } if (fname === Settings_1.Settings.SQRT) { v[index] = '\\sqrt' + this.braces(input.join(',')); } else if (fname === Settings_1.Settings.ABS) { v[index] = this.brackets(input.join(','), 'abs'); } else if (fname === Settings_1.Settings.PARENTHESIS) { v[index] = this.brackets(input.join(','), 'parens'); } else if (fname === 'limit') { v[index] = ' \\lim\\limits_{' + input[1] + ' \\to ' + input[2] + '} ' + input[0]; } else if (fname === 'integrate') { v[index] = '\\int' + this.braces(input[0]) + this.braces('d' + input[1]); } else if (fname === 'defint') { v[index] = '\\int\\limits_' + this.braces(input[1]) + '^' + this.braces(input[2]) + ' ' + input[0] + ' d' + input[3]; } else if (fname === Settings_1.Settings.FACTORIAL || fname === Settings_1.Settings.DOUBLEFACTORIAL) { var arg = symbol.args[0]; if (arg.power.equals(1) && (arg.isComposite() || arg.isCombination())) { input[0] = this.brackets(input[0]); } v[index] = input[0] + (fname === Settings_1.Settings.FACTORIAL ? '!' : '!!'); } else if (fname === 'floor') { v[index] = '\\left \\lfloor' + this.braces(input[0]) + '\\right \\rfloor'; } else if (fname === 'ceil') { v[index] = '\\left \\lceil' + this.braces(input[0]) + '\\right \\rceil'; } // capture log(a, b) else if (fname === Settings_1.Settings.LOG && input.length > 1) { v[index] = '\\mathrm' + this.braces(Settings_1.Settings.LOG) + '_' + this.braces(input[1]) + this.brackets(input[0]); } // capture log(a, b) else if (fname === Settings_1.Settings.LOG10) { v[index] = '\\mathrm' + this.braces(Settings_1.Settings.LOG) + '_' + this.braces(10) + this.brackets(input[0]); } else if (fname === 'sum') { var a = input[0], b = input[1], c = input[2], d = input[3]; v[index] = '\\sum\\limits_{' + this.braces(b) + '=' + this.braces(c) + '}^' + this.braces(d) + ' ' + this.braces(a) + ''; } else if (fname === 'product') { var a = input[0], b = input[1], c = input[2], d = input[3]; v[index] = '\\prod\\limits_{' + this.braces(b) + '=' + this.braces(c) + '}^' + this.braces(d) + ' ' + this.braces(a) + ''; } else if (fname === 'nthroot') { v[index] = '\\sqrt[' + input[1] + ']' + this.braces(input[0]); } else if (fname === 'mod') { v[index] = input[0] + ' \\bmod ' + input[1]; } else if (fname === 'realpart') { v[index] = '\\operatorname{Re}' + this.brackets(input[0]); } else if (fname === 'imagpart') { v[index] = '\\operatorname{Im}' + this.brackets(input[0]); } else { var name = fname !== '' ? '\\mathrm' + this.braces(fname.replace(/_/g, '\\_')) : ''; if (symbol.isConversion) v[index] = name + this.brackets(input.join(''), 'parens'); else v[index] = name + this.brackets(input.join(','), 'parens'); } } else if (symbol.isComposite()) { var collected = symbol.collectSymbols().sort(group === Groups_1.Groups.CP || previousGroup === Groups_1.Groups.CP ? function (a, b) { return b.group - a.group; } : function (a, b) { var x = (0, Utils_1.isSymbol)(a.power) ? -1 : a.power; var y = (0, Utils_1.isSymbol)(b.power) ? -1 : b.power; return y - x; }), symbols = [], l = collected.length; for (var i = 0; i < l; i++) { symbols.push(LaTeX.latex(collected[i], option)); } var value = symbols.join('+'); v[index] = !(symbol.isLinear() && symbol.multiplier.equals(1)) || negative ? this.brackets(value, 'parens') : value; } else if (group === Groups_1.Groups.CB || previousGroup === Groups_1.Groups.EX || previousGroup === Groups_1.Groups.CB) { if (group === Groups_1.Groups.CB) symbol.distributeExponent(); // This almost feels a little like cheating but I need to know if I should be wrapping the symbol // in brackets or not. We'll do this by checking the value of the numerator and then comparing it // to whether the symbol value is "simple" or not. var denominator = [], numerator = []; // Generate a profile var den_map = [], num_map = [], num_c = 0, den_c = 0; var setBrackets = function (container, map, counter) { if (counter > 1 && map.length > 0) { var l = map.length; for (var i = 0; i < l; i++) { var idx = map[i], item = container[idx]; if (!(/^\\left\(.+\\right\)\^\{.+\}$/g.test(item) || /^\\left\(.+\\right\)$/g.test(item))) { container[idx] = LaTeX.brackets(item, 'parens'); } } } return container; }; // Generate latex for each of them symbol.each(function (x) { var isDenom = (0, Utils_1.isNegative)(x.power), laTex; if (isDenom) { laTex = LaTeX.latex(x.invert(), option); den_c++; if (x.isComposite()) { if (symbol.multiplier.den != 1 && Math.abs(x.power) == 1) laTex = LaTeX.brackets(laTex, 'parens'); den_map.push(denominator.length); // make a note of where the composite was found } denominator.push(laTex); } else { laTex = LaTeX.latex(x, option); num_c++; if (x.isComposite()) { if (symbol.multiplier.num != 1 && Math.abs(x.power) == 1) laTex = LaTeX.brackets(laTex, 'parens'); num_map.push(numerator.length); // make a note of where the composite was found } numerator.push(laTex); } }); // Apply brackets setBrackets(numerator, num_map, num_c); v[0] = numerator.join(this.dot); // collapse the numerator into one string setBrackets(denominator, den_map, den_c); v[1] = denominator.join(this.dot); } return v; } static set(m, v, p, combine_power) { var isBracketed = function (v) { return /^\\left\(.+\\right\)$/.test(v); }; // format the power if it exists if (p) p = this.formatP(p); // group Groups.CB will have to be wrapped since the power applies to both it's numerator and denominator if (combine_power) { // POSSIBLE BUG: If powers for group Groups.CB format wrong, investigate this since I might have overlooked something // the assumption is that in every case the denonimator should be empty when dealing with CB. I can't think // of a case where this isn't true var tp = p[0]; p[0] = ''; // temporarily make p blank } // merge v and p. Not that v MUST be first since the order matters v = this.merge(v, p); var mn = m[0], md = m[1], vn = v[0], vd = v[1]; // filters // if the top has a variable but the numerator is one drop it if (vn && Number(mn) === 1) mn = ''; // if denominator is 1 drop it always if (Number(md) === 1) md = ''; // prepare the top portion but check that it's not already bracketed. If it is then leave out the cdot var top = this.join(mn, vn, !isBracketed(vn) ? this.dot : ''); // prepare the bottom portion but check that it's not already bracketed. If it is then leave out the cdot var bottom = this.join(md, vd, !isBracketed(vd) ? this.dot : ''); // format the power if it exists // make it a fraction if both top and bottom exists if (top && bottom) { var frac = this.frac(top, bottom); if (combine_power && tp) frac = this.brackets(frac) + tp; return frac; } // otherwise only the top exists so return that else return top; } static merge(a, b) { var r = []; for (var i = 0; i < 2; i++) r[i] = a[i] + b[i]; return r; } // joins together two strings if both exist static join(n, d, glue) { if (!n && !d) return ''; if (n && !d) return n; if (d && !n) return d; return n + glue + d; } /** * Places subscripts in braces for proper formatting * @param {String} v * @returns {String} */ static formatSubscripts(v) { // Split it at the underscore var arr = v.toString().split('_'); var name = ''; // Loop over all entries except the first one while (arr.length > 1) { // Wrap all in braces except for the last one if (arr.length > 0) { name = '_' + this.braces(arr.pop() + name); } } return arr[0] + name; } static formatP(p_array) { for (var i = 0; i < 2; i++) { var p = p_array[i]; if (p) p_array[i] = '^' + this.braces(p); } return p_array; } /** * formats the fractions accordingly. * @param {Frac} f * @param {boolean} is_pow */ static formatFrac(f, is_pow) { var n = f.num.toString(), d = f.den.toString(); // no need to have x^1 if (is_pow && n === '1' && d === '1') return ''; // no need to have x/1 if (d === '1') return n; return this.frac(n, d); } static frac(n, d) { return '\\frac' + this.braces(n) + this.braces(d); } static braces(e) { return '{' + e + '}'; } static brackets(e, typ) { typ = typ || 'parens'; var bracketTypes = { parens: ['(', ')'], square: ['[', ']'], brace: ['{', '}'], abs: ['|', '|'], angle: ['\\langle', '\\rangle'] }; var bracket = bracketTypes[typ]; return '\\left' + bracket[0] + e + '\\right' + bracket[1]; } /** * Removes extreneous tokens * @param {Tokens[]} tokens * @returns {Tokens[]} */ static filterTokens(tokens) { var filtered = []; // Copy over the type of the scope if (Array.isArray(tokens)) { filtered.type = tokens.type; } // the items that need to be disposed var d = ['\\', 'left', 'right', 'big', 'Big', 'large', 'Large']; for (var i = 0, l = tokens.length; i < l; i++) { var token = tokens[i]; var next_token = tokens[i + 1]; if (token.value === '\\' && next_token.value === '\\') { filtered.push(token); } else if (Array.isArray(token)) { filtered.push(LaTeX.filterTokens(token)); } else if (d.indexOf(token.value) === -1) { filtered.push(token); } } return filtered; } /* * Parses tokens from LaTeX string. Does not do any error checking * @param {Tokens[]} rpn * @returns {String} */ static parse(raw_tokens) { var i, l; var retval = ''; var tokens = this.filterTokens(raw_tokens); var replace = { 'cdot': '', 'times': '', 'infty': 'Infinity' }; // get the next token var next = function (n) { return tokens[(typeof n === 'undefined' ? ++i : i += n)]; }; var parse_next = function () { return LaTeX.parse(next()); }; var get = function (token) { if (token in replace) { return replace[token]; } // A quirk with implicit multiplication forces us to check for * if (token === '*' && tokens[i + 1].value === '&') { next(2); // skip this and the & return ','; } if (token === '&') { next(); return ','; // Skip the * } // If it's the end of a row, return the row separator if (token === '\\') { return '],['; } return token; }; // start parsing the tokens for (i = 0, l = tokens.length; i < l; i++) { var token = tokens[i]; // fractions if (token.value === 'frac') { // parse and wrap it in brackets var n = parse_next(); var d = parse_next(); retval += n + '/' + d; } else if (token.value in LaTeX.symbols) { if (token.value === Settings_1.Settings.SQRT && tokens[i + 1].type === 'vector' && tokens[i + 2].type === 'Set') { var base = parse_next(); var expr = parse_next(); retval += (expr + '^' + (0, Utils_1.inBrackets)('1/' + base)); } else { retval += token.value + parse_next(); } } else if (token.value === 'int') { var f = parse_next(); // skip the comma i++; // get the variable of integration var dx = next().value; dx = get(dx.substring(1, dx.length)); retval += 'integrate' + (0, Utils_1.inBrackets)(f + ',' + dx); } else if (token.value === 'int_') { var l = parse_next(); // lower i++; // skip the ^ var u = next().value; // upper // if it is in brackets if (u === undefined) { i--; var u = parse_next(); } var f = parse_next(); // function // get the variable of integration var dx = next().value; // skip the comma if (dx === ',') { var dx = next().value; } // if 'd', skip if (dx === 'differentialD') { // skip the * i++; var dx = next().value; } if (dx === 'mathrm') { // skip the mathrm{d} i++; var dx = next().value; } retval += 'defint' + (0, Utils_1.inBrackets)(f + ',' + l + ',' + u + ',' + dx); } else if (token.value && token.value.startsWith('int_')) { // var l = parse_next(); // lower var l = token.value.replace('int_', ''); console.log('uppernow'); i++; // skip the ^ var u = next().value; // upper // if it is in brackets if (u === undefined) { i--; var u = parse_next(); } var f = parse_next(); // function // get the variable of integration var dx = next().value; // skip the comma if (dx === ',') { var dx = next().value; } // if 'd', skip if (dx === 'differentialD') { // skip the * i++; var dx = next().value; } if (dx === 'mathrm') { // skip the mathrm{d} i++; var dx = next().value; } retval += 'defint' + (0, Utils_1.inBrackets)(f + ',' + l + ',' + u + ',' + dx); } else if (token.value === 'mathrm') { var f = tokens[++i][0].value; retval += f + parse_next(); } // sum and product else if (token.value === 'sum_' || token.value === 'prod_') { var fn = token.value === 'sum_' ? 'sum' : 'product'; var nxt = next(); i++; // skip the caret var end = parse_next(); var f = parse_next(); retval += fn + (0, Utils_1.inBrackets)([f, get(nxt[0]), get(nxt[2]), get(end)].join(',')); } else if (token.value === 'lim_') { var nxt = next(); retval += 'limit' + (0, Utils_1.inBrackets)([parse_next(), get(nxt[0]), get(nxt[2])].join(',')); } else if (token.value === 'begin') { var nxt = next(); if (Array.isArray(nxt)) { var v = nxt[0].value; if (v === 'matrix') { // Start a matrix retval += 'matrix(['; } } } else if (token.value === 'end') { var nxt = next(); if (Array.isArray(nxt)) { var v = nxt[0].value; if (v === 'matrix') { // End a matrix retval += '])'; } } } else { if (Array.isArray(token)) { retval += get(LaTeX.parse(token)); } else { retval += get(token.value.toString()); } } } return (0, Utils_1.inBrackets)(retval); } } exports.LaTeX = LaTeX; /** @deprecated */ LaTeX.space = '~'; LaTeX.dot = ' \\cdot '; LaTeX.greek = { alpha: '\\alpha', beta: '\\beta', gamma: '\\gamma', delta: '\\delta', epsilon: '\\epsilon', zeta: '\\zeta', eta: '\\eta', theta: '\\theta', iota: '\\iota', kappa: '\\kappa', lambda: '\\lambda', mu: '\\mu', nu: '\\nu', xi: '\\xi', omnikron: '\\omnikron', pi: '\\pi', rho: '\\rho', sigma: '\\sigma', tau: '\\tau', upsilon: '\\upsilon', phi: '\\phi', chi: '\\chi', psi: '\\psi', omega: '\\omega', Gamma: '\\Gamma', Delta: '\\Delta', Epsilon: '\\Epsilon', Theta: '\\Theta', Lambda: '\\Lambda', Xi: '\\Xi', Pi: '\\Pi', Sigma: '\\Sigma', Phi: '\\Phi', Psi: '\\Psi', Omega: '\\Omega' }; LaTeX.symbols = { arccos: '\\arccos', cos: '\\cos', csc: '\\csc', exp: '\\exp', ker: '\\ker', limsup: '\\limsup', min: '\\min', sinh: '\\sinh', arcsin: '\\arcsin', cosh: '\\cosh', deg: '\\deg', gcd: '\\gcd', lg: '\\lg', ln: '\\ln', Pr: '\\Pr', sqrt: '\\sqrt', sup: '\\sup', arctan: '\\arctan', cot: '\\cot', det: '\\det', hom: '\\hom', lim: '\\lim', log: '\\log', LN: '\\LN', sec: '\\sec', tan: '\\tan', arg: '\\arg', coth: '\\coth', dim: '\\dim', inf: '\\inf', liminf: '\\liminf', max: '\\max', sin: '\\sin', tanh: '\\tanh' }; //# sourceMappingURL=LaTeX.js.map