"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.createParse = void 0; var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var _factory = require("../utils/factory.js"); var _is = require("../utils/is.js"); var _collection = require("../utils/collection.js"); var _object = require("../utils/object.js"); var name = 'parse'; var dependencies = ['typed', 'numeric', 'config', 'AccessorNode', 'ArrayNode', 'AssignmentNode', 'BlockNode', 'ConditionalNode', 'ConstantNode', 'FunctionAssignmentNode', 'FunctionNode', 'IndexNode', 'ObjectNode', 'OperatorNode', 'ParenthesisNode', 'RangeNode', 'RelationalNode', 'SymbolNode']; var createParse = /* #__PURE__ */(0, _factory.factory)(name, dependencies, function (_ref) { var typed = _ref.typed, numeric = _ref.numeric, config = _ref.config, AccessorNode = _ref.AccessorNode, ArrayNode = _ref.ArrayNode, AssignmentNode = _ref.AssignmentNode, BlockNode = _ref.BlockNode, ConditionalNode = _ref.ConditionalNode, ConstantNode = _ref.ConstantNode, FunctionAssignmentNode = _ref.FunctionAssignmentNode, FunctionNode = _ref.FunctionNode, IndexNode = _ref.IndexNode, ObjectNode = _ref.ObjectNode, OperatorNode = _ref.OperatorNode, ParenthesisNode = _ref.ParenthesisNode, RangeNode = _ref.RangeNode, RelationalNode = _ref.RelationalNode, SymbolNode = _ref.SymbolNode; /** * Parse an expression. Returns a node tree, which can be evaluated by * invoking node.evaluate(). * * Note the evaluating arbitrary expressions may involve security risks, * see [https://mathjs.org/docs/expressions/security.html](https://mathjs.org/docs/expressions/security.html) for more information. * * Syntax: * * math.parse(expr) * math.parse(expr, options) * math.parse([expr1, expr2, expr3, ...]) * math.parse([expr1, expr2, expr3, ...], options) * * Example: * * const node1 = math.parse('sqrt(3^2 + 4^2)') * node1.compile().evaluate() // 5 * * let scope = {a:3, b:4} * const node2 = math.parse('a * b') // 12 * const code2 = node2.compile() * code2.evaluate(scope) // 12 * scope.a = 5 * code2.evaluate(scope) // 20 * * const nodes = math.parse(['a = 3', 'b = 4', 'a * b']) * nodes[2].compile().evaluate() // 12 * * See also: * * evaluate, compile * * @param {string | string[] | Matrix} expr Expression to be parsed * @param {{nodes: Object}} [options] Available options: * - `nodes` a set of custom nodes * @return {Node | Node[]} node * @throws {Error} */ var parse = typed(name, { string: function string(expression) { return parseStart(expression, {}); }, 'Array | Matrix': function ArrayMatrix(expressions) { return parseMultiple(expressions, {}); }, 'string, Object': function stringObject(expression, options) { var extraNodes = options.nodes !== undefined ? options.nodes : {}; return parseStart(expression, extraNodes); }, 'Array | Matrix, Object': parseMultiple }); function parseMultiple(expressions) { var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var extraNodes = options.nodes !== undefined ? options.nodes : {}; // parse an array or matrix with expressions return (0, _collection.deepMap)(expressions, function (elem) { if (typeof elem !== 'string') throw new TypeError('String expected'); return parseStart(elem, extraNodes); }); } // token types enumeration var TOKENTYPE = { NULL: 0, DELIMITER: 1, NUMBER: 2, SYMBOL: 3, UNKNOWN: 4 }; // map with all delimiters var DELIMITERS = { ',': true, '(': true, ')': true, '[': true, ']': true, '{': true, '}': true, '"': true, '\'': true, ';': true, '+': true, '-': true, '*': true, '.*': true, '/': true, './': true, '%': true, '^': true, '.^': true, '~': true, '!': true, '&': true, '|': true, '^|': true, '=': true, ':': true, '?': true, '==': true, '!=': true, '<': true, '>': true, '<=': true, '>=': true, '<<': true, '>>': true, '>>>': true }; // map with all named delimiters var NAMED_DELIMITERS = { mod: true, to: true, in: true, and: true, xor: true, or: true, not: true }; var CONSTANTS = { true: true, false: false, null: null, undefined: undefined }; var NUMERIC_CONSTANTS = ['NaN', 'Infinity']; function initialState() { return { extraNodes: {}, // current extra nodes, must be careful not to mutate expression: '', // current expression comment: '', // last parsed comment index: 0, // current index in expr token: '', // current token tokenType: TOKENTYPE.NULL, // type of the token nestingLevel: 0, // level of nesting inside parameters, used to ignore newline characters conditionalLevel: null // when a conditional is being parsed, the level of the conditional is stored here }; } /** * View upto `length` characters of the expression starting at the current character. * * @param {Object} state * @param {number} [length=1] Number of characters to view * @returns {string} * @private */ function currentString(state, length) { return state.expression.substr(state.index, length); } /** * View the current character. Returns '' if end of expression is reached. * * @param {Object} state * @returns {string} * @private */ function currentCharacter(state) { return currentString(state, 1); } /** * Get the next character from the expression. * The character is stored into the char c. If the end of the expression is * reached, the function puts an empty string in c. * @private */ function next(state) { state.index++; } /** * Preview the previous character from the expression. * @return {string} cNext * @private */ function prevCharacter(state) { return state.expression.charAt(state.index - 1); } /** * Preview the next character from the expression. * @return {string} cNext * @private */ function nextCharacter(state) { return state.expression.charAt(state.index + 1); } /** * Get next token in the current string expr. * The token and token type are available as token and tokenType * @private */ function getToken(state) { state.tokenType = TOKENTYPE.NULL; state.token = ''; state.comment = ''; // skip over ignored characters: while (true) { // comments: if (currentCharacter(state) === '#') { while (currentCharacter(state) !== '\n' && currentCharacter(state) !== '') { state.comment += currentCharacter(state); next(state); } } // whitespace: space, tab, and newline when inside parameters if (parse.isWhitespace(currentCharacter(state), state.nestingLevel)) { next(state); } else { break; } } // check for end of expression if (currentCharacter(state) === '') { // token is still empty state.tokenType = TOKENTYPE.DELIMITER; return; } // check for new line character if (currentCharacter(state) === '\n' && !state.nestingLevel) { state.tokenType = TOKENTYPE.DELIMITER; state.token = currentCharacter(state); next(state); return; } var c1 = currentCharacter(state); var c2 = currentString(state, 2); var c3 = currentString(state, 3); if (c3.length === 3 && DELIMITERS[c3]) { state.tokenType = TOKENTYPE.DELIMITER; state.token = c3; next(state); next(state); next(state); return; } // check for delimiters consisting of 2 characters if (c2.length === 2 && DELIMITERS[c2]) { state.tokenType = TOKENTYPE.DELIMITER; state.token = c2; next(state); next(state); return; } // check for delimiters consisting of 1 character if (DELIMITERS[c1]) { state.tokenType = TOKENTYPE.DELIMITER; state.token = c1; next(state); return; } // check for a number if (parse.isDigitDot(c1)) { state.tokenType = TOKENTYPE.NUMBER; // check for binary, octal, or hex var _c = currentString(state, 2); if (_c === '0b' || _c === '0o' || _c === '0x') { state.token += currentCharacter(state); next(state); state.token += currentCharacter(state); next(state); while (parse.isHexDigit(currentCharacter(state))) { state.token += currentCharacter(state); next(state); } if (currentCharacter(state) === '.') { // this number has a radix point state.token += '.'; next(state); // get the digits after the radix while (parse.isHexDigit(currentCharacter(state))) { state.token += currentCharacter(state); next(state); } } else if (currentCharacter(state) === 'i') { // this number has a word size suffix state.token += 'i'; next(state); // get the word size while (parse.isDigit(currentCharacter(state))) { state.token += currentCharacter(state); next(state); } } return; } // get number, can have a single dot if (currentCharacter(state) === '.') { state.token += currentCharacter(state); next(state); if (!parse.isDigit(currentCharacter(state))) { // this is no number, it is just a dot (can be dot notation) state.tokenType = TOKENTYPE.DELIMITER; return; } } else { while (parse.isDigit(currentCharacter(state))) { state.token += currentCharacter(state); next(state); } if (parse.isDecimalMark(currentCharacter(state), nextCharacter(state))) { state.token += currentCharacter(state); next(state); } } while (parse.isDigit(currentCharacter(state))) { state.token += currentCharacter(state); next(state); } // check for exponential notation like "2.3e-4", "1.23e50" or "2e+4" if (currentCharacter(state) === 'E' || currentCharacter(state) === 'e') { if (parse.isDigit(nextCharacter(state)) || nextCharacter(state) === '-' || nextCharacter(state) === '+') { state.token += currentCharacter(state); next(state); if (currentCharacter(state) === '+' || currentCharacter(state) === '-') { state.token += currentCharacter(state); next(state); } // Scientific notation MUST be followed by an exponent if (!parse.isDigit(currentCharacter(state))) { throw createSyntaxError(state, 'Digit expected, got "' + currentCharacter(state) + '"'); } while (parse.isDigit(currentCharacter(state))) { state.token += currentCharacter(state); next(state); } if (parse.isDecimalMark(currentCharacter(state), nextCharacter(state))) { throw createSyntaxError(state, 'Digit expected, got "' + currentCharacter(state) + '"'); } } else if (nextCharacter(state) === '.') { next(state); throw createSyntaxError(state, 'Digit expected, got "' + currentCharacter(state) + '"'); } } return; } // check for variables, functions, named operators if (parse.isAlpha(currentCharacter(state), prevCharacter(state), nextCharacter(state))) { while (parse.isAlpha(currentCharacter(state), prevCharacter(state), nextCharacter(state)) || parse.isDigit(currentCharacter(state))) { state.token += currentCharacter(state); next(state); } if ((0, _object.hasOwnProperty)(NAMED_DELIMITERS, state.token)) { state.tokenType = TOKENTYPE.DELIMITER; } else { state.tokenType = TOKENTYPE.SYMBOL; } return; } // something unknown is found, wrong characters -> a syntax error state.tokenType = TOKENTYPE.UNKNOWN; while (currentCharacter(state) !== '') { state.token += currentCharacter(state); next(state); } throw createSyntaxError(state, 'Syntax error in part "' + state.token + '"'); } /** * Get next token and skip newline tokens */ function getTokenSkipNewline(state) { do { getToken(state); } while (state.token === '\n'); // eslint-disable-line no-unmodified-loop-condition } /** * Open parameters. * New line characters will be ignored until closeParams(state) is called */ function openParams(state) { state.nestingLevel++; } /** * Close parameters. * New line characters will no longer be ignored */ function closeParams(state) { state.nestingLevel--; } /** * Checks whether the current character `c` is a valid alpha character: * * - A latin letter (upper or lower case) Ascii: a-z, A-Z * - An underscore Ascii: _ * - A dollar sign Ascii: $ * - A latin letter with accents Unicode: \u00C0 - \u02AF * - A greek letter Unicode: \u0370 - \u03FF * - A mathematical alphanumeric symbol Unicode: \u{1D400} - \u{1D7FF} excluding invalid code points * * The previous and next characters are needed to determine whether * this character is part of a unicode surrogate pair. * * @param {string} c Current character in the expression * @param {string} cPrev Previous character * @param {string} cNext Next character * @return {boolean} */ parse.isAlpha = function isAlpha(c, cPrev, cNext) { return parse.isValidLatinOrGreek(c) || parse.isValidMathSymbol(c, cNext) || parse.isValidMathSymbol(cPrev, c); }; /** * Test whether a character is a valid latin, greek, or letter-like character * @param {string} c * @return {boolean} */ parse.isValidLatinOrGreek = function isValidLatinOrGreek(c) { return /^[a-zA-Z_$\u00C0-\u02AF\u0370-\u03FF\u2100-\u214F]$/.test(c); }; /** * Test whether two given 16 bit characters form a surrogate pair of a * unicode math symbol. * * https://unicode-table.com/en/ * https://www.wikiwand.com/en/Mathematical_operators_and_symbols_in_Unicode * * Note: In ES6 will be unicode aware: * https://stackoverflow.com/questions/280712/javascript-unicode-regexes * https://mathiasbynens.be/notes/es6-unicode-regex * * @param {string} high * @param {string} low * @return {boolean} */ parse.isValidMathSymbol = function isValidMathSymbol(high, low) { return /^[\uD835]$/.test(high) && /^[\uDC00-\uDFFF]$/.test(low) && /^[^\uDC55\uDC9D\uDCA0\uDCA1\uDCA3\uDCA4\uDCA7\uDCA8\uDCAD\uDCBA\uDCBC\uDCC4\uDD06\uDD0B\uDD0C\uDD15\uDD1D\uDD3A\uDD3F\uDD45\uDD47-\uDD49\uDD51\uDEA6\uDEA7\uDFCC\uDFCD]$/.test(low); }; /** * Check whether given character c is a white space character: space, tab, or enter * @param {string} c * @param {number} nestingLevel * @return {boolean} */ parse.isWhitespace = function isWhitespace(c, nestingLevel) { // TODO: also take '\r' carriage return as newline? Or does that give problems on mac? return c === ' ' || c === '\t' || c === '\n' && nestingLevel > 0; }; /** * Test whether the character c is a decimal mark (dot). * This is the case when it's not the start of a delimiter '.*', './', or '.^' * @param {string} c * @param {string} cNext * @return {boolean} */ parse.isDecimalMark = function isDecimalMark(c, cNext) { return c === '.' && cNext !== '/' && cNext !== '*' && cNext !== '^'; }; /** * checks if the given char c is a digit or dot * @param {string} c a string with one character * @return {boolean} */ parse.isDigitDot = function isDigitDot(c) { return c >= '0' && c <= '9' || c === '.'; }; /** * checks if the given char c is a digit * @param {string} c a string with one character * @return {boolean} */ parse.isDigit = function isDigit(c) { return c >= '0' && c <= '9'; }; /** * checks if the given char c is a hex digit * @param {string} c a string with one character * @return {boolean} */ parse.isHexDigit = function isHexDigit(c) { return c >= '0' && c <= '9' || c >= 'a' && c <= 'f' || c >= 'A' && c <= 'F'; }; /** * Start of the parse levels below, in order of precedence * @return {Node} node * @private */ function parseStart(expression, extraNodes) { var state = initialState(); (0, _extends2.default)(state, { expression: expression, extraNodes: extraNodes }); getToken(state); var node = parseBlock(state); // check for garbage at the end of the expression // an expression ends with a empty character '' and tokenType DELIMITER if (state.token !== '') { if (state.tokenType === TOKENTYPE.DELIMITER) { // user entered a not existing operator like "//" // TODO: give hints for aliases, for example with "<>" give as hint " did you mean !== ?" throw createError(state, 'Unexpected operator ' + state.token); } else { throw createSyntaxError(state, 'Unexpected part "' + state.token + '"'); } } return node; } /** * Parse a block with expressions. Expressions can be separated by a newline * character '\n', or by a semicolon ';'. In case of a semicolon, no output * of the preceding line is returned. * @return {Node} node * @private */ function parseBlock(state) { var node; var blocks = []; var visible; if (state.token !== '' && state.token !== '\n' && state.token !== ';') { node = parseAssignment(state); node.comment = state.comment; } // TODO: simplify this loop while (state.token === '\n' || state.token === ';') { // eslint-disable-line no-unmodified-loop-condition if (blocks.length === 0 && node) { visible = state.token !== ';'; blocks.push({ node: node, visible: visible }); } getToken(state); if (state.token !== '\n' && state.token !== ';' && state.token !== '') { node = parseAssignment(state); node.comment = state.comment; visible = state.token !== ';'; blocks.push({ node: node, visible: visible }); } } if (blocks.length > 0) { return new BlockNode(blocks); } else { if (!node) { node = new ConstantNode(undefined); node.comment = state.comment; } return node; } } /** * Assignment of a function or variable, * - can be a variable like 'a=2.3' * - or a updating an existing variable like 'matrix(2,3:5)=[6,7,8]' * - defining a function like 'f(x) = x^2' * @return {Node} node * @private */ function parseAssignment(state) { var name, args, value, valid; var node = parseConditional(state); if (state.token === '=') { if ((0, _is.isSymbolNode)(node)) { // parse a variable assignment like 'a = 2/3' name = node.name; getTokenSkipNewline(state); value = parseAssignment(state); return new AssignmentNode(new SymbolNode(name), value); } else if ((0, _is.isAccessorNode)(node)) { // parse a matrix subset assignment like 'A[1,2] = 4' getTokenSkipNewline(state); value = parseAssignment(state); return new AssignmentNode(node.object, node.index, value); } else if ((0, _is.isFunctionNode)(node) && (0, _is.isSymbolNode)(node.fn)) { // parse function assignment like 'f(x) = x^2' valid = true; args = []; name = node.name; node.args.forEach(function (arg, index) { if ((0, _is.isSymbolNode)(arg)) { args[index] = arg.name; } else { valid = false; } }); if (valid) { getTokenSkipNewline(state); value = parseAssignment(state); return new FunctionAssignmentNode(name, args, value); } } throw createSyntaxError(state, 'Invalid left hand side of assignment operator ='); } return node; } /** * conditional operation * * condition ? truePart : falsePart * * Note: conditional operator is right-associative * * @return {Node} node * @private */ function parseConditional(state) { var node = parseLogicalOr(state); while (state.token === '?') { // eslint-disable-line no-unmodified-loop-condition // set a conditional level, the range operator will be ignored as long // as conditionalLevel === state.nestingLevel. var prev = state.conditionalLevel; state.conditionalLevel = state.nestingLevel; getTokenSkipNewline(state); var condition = node; var trueExpr = parseAssignment(state); if (state.token !== ':') throw createSyntaxError(state, 'False part of conditional expression expected'); state.conditionalLevel = null; getTokenSkipNewline(state); var falseExpr = parseAssignment(state); // Note: check for conditional operator again, right associativity node = new ConditionalNode(condition, trueExpr, falseExpr); // restore the previous conditional level state.conditionalLevel = prev; } return node; } /** * logical or, 'x or y' * @return {Node} node * @private */ function parseLogicalOr(state) { var node = parseLogicalXor(state); while (state.token === 'or') { // eslint-disable-line no-unmodified-loop-condition getTokenSkipNewline(state); node = new OperatorNode('or', 'or', [node, parseLogicalXor(state)]); } return node; } /** * logical exclusive or, 'x xor y' * @return {Node} node * @private */ function parseLogicalXor(state) { var node = parseLogicalAnd(state); while (state.token === 'xor') { // eslint-disable-line no-unmodified-loop-condition getTokenSkipNewline(state); node = new OperatorNode('xor', 'xor', [node, parseLogicalAnd(state)]); } return node; } /** * logical and, 'x and y' * @return {Node} node * @private */ function parseLogicalAnd(state) { var node = parseBitwiseOr(state); while (state.token === 'and') { // eslint-disable-line no-unmodified-loop-condition getTokenSkipNewline(state); node = new OperatorNode('and', 'and', [node, parseBitwiseOr(state)]); } return node; } /** * bitwise or, 'x | y' * @return {Node} node * @private */ function parseBitwiseOr(state) { var node = parseBitwiseXor(state); while (state.token === '|') { // eslint-disable-line no-unmodified-loop-condition getTokenSkipNewline(state); node = new OperatorNode('|', 'bitOr', [node, parseBitwiseXor(state)]); } return node; } /** * bitwise exclusive or (xor), 'x ^| y' * @return {Node} node * @private */ function parseBitwiseXor(state) { var node = parseBitwiseAnd(state); while (state.token === '^|') { // eslint-disable-line no-unmodified-loop-condition getTokenSkipNewline(state); node = new OperatorNode('^|', 'bitXor', [node, parseBitwiseAnd(state)]); } return node; } /** * bitwise and, 'x & y' * @return {Node} node * @private */ function parseBitwiseAnd(state) { var node = parseRelational(state); while (state.token === '&') { // eslint-disable-line no-unmodified-loop-condition getTokenSkipNewline(state); node = new OperatorNode('&', 'bitAnd', [node, parseRelational(state)]); } return node; } /** * Parse a chained conditional, like 'a > b >= c' * @return {Node} node */ function parseRelational(state) { var params = [parseShift(state)]; var conditionals = []; var operators = { '==': 'equal', '!=': 'unequal', '<': 'smaller', '>': 'larger', '<=': 'smallerEq', '>=': 'largerEq' }; while ((0, _object.hasOwnProperty)(operators, state.token)) { // eslint-disable-line no-unmodified-loop-condition var cond = { name: state.token, fn: operators[state.token] }; conditionals.push(cond); getTokenSkipNewline(state); params.push(parseShift(state)); } if (params.length === 1) { return params[0]; } else if (params.length === 2) { return new OperatorNode(conditionals[0].name, conditionals[0].fn, params); } else { return new RelationalNode(conditionals.map(function (c) { return c.fn; }), params); } } /** * Bitwise left shift, bitwise right arithmetic shift, bitwise right logical shift * @return {Node} node * @private */ function parseShift(state) { var node, name, fn, params; node = parseConversion(state); var operators = { '<<': 'leftShift', '>>': 'rightArithShift', '>>>': 'rightLogShift' }; while ((0, _object.hasOwnProperty)(operators, state.token)) { name = state.token; fn = operators[name]; getTokenSkipNewline(state); params = [node, parseConversion(state)]; node = new OperatorNode(name, fn, params); } return node; } /** * conversion operators 'to' and 'in' * @return {Node} node * @private */ function parseConversion(state) { var node, name, fn, params; node = parseRange(state); var operators = { to: 'to', in: 'to' // alias of 'to' }; while ((0, _object.hasOwnProperty)(operators, state.token)) { name = state.token; fn = operators[name]; getTokenSkipNewline(state); if (name === 'in' && state.token === '') { // end of expression -> this is the unit 'in' ('inch') node = new OperatorNode('*', 'multiply', [node, new SymbolNode('in')], true); } else { // operator 'a to b' or 'a in b' params = [node, parseRange(state)]; node = new OperatorNode(name, fn, params); } } return node; } /** * parse range, "start:end", "start:step:end", ":", "start:", ":end", etc * @return {Node} node * @private */ function parseRange(state) { var node; var params = []; if (state.token === ':') { // implicit start=1 (one-based) node = new ConstantNode(1); } else { // explicit start node = parseAddSubtract(state); } if (state.token === ':' && state.conditionalLevel !== state.nestingLevel) { // we ignore the range operator when a conditional operator is being processed on the same level params.push(node); // parse step and end while (state.token === ':' && params.length < 3) { // eslint-disable-line no-unmodified-loop-condition getTokenSkipNewline(state); if (state.token === ')' || state.token === ']' || state.token === ',' || state.token === '') { // implicit end params.push(new SymbolNode('end')); } else { // explicit end params.push(parseAddSubtract(state)); } } if (params.length === 3) { // params = [start, step, end] node = new RangeNode(params[0], params[2], params[1]); // start, end, step } else { // length === 2 // params = [start, end] node = new RangeNode(params[0], params[1]); // start, end } } return node; } /** * add or subtract * @return {Node} node * @private */ function parseAddSubtract(state) { var node, name, fn, params; node = parseMultiplyDivide(state); var operators = { '+': 'add', '-': 'subtract' }; while ((0, _object.hasOwnProperty)(operators, state.token)) { name = state.token; fn = operators[name]; getTokenSkipNewline(state); var rightNode = parseMultiplyDivide(state); if (rightNode.isPercentage) { params = [node, new OperatorNode('*', 'multiply', [node, rightNode])]; } else { params = [node, rightNode]; } node = new OperatorNode(name, fn, params); } return node; } /** * multiply, divide, modulus * @return {Node} node * @private */ function parseMultiplyDivide(state) { var node, last, name, fn; node = parseImplicitMultiplication(state); last = node; var operators = { '*': 'multiply', '.*': 'dotMultiply', '/': 'divide', './': 'dotDivide' }; while (true) { if ((0, _object.hasOwnProperty)(operators, state.token)) { // explicit operators name = state.token; fn = operators[name]; getTokenSkipNewline(state); last = parseImplicitMultiplication(state); node = new OperatorNode(name, fn, [node, last]); } else { break; } } return node; } /** * implicit multiplication * @return {Node} node * @private */ function parseImplicitMultiplication(state) { var node, last; node = parseRule2(state); last = node; while (true) { if (state.tokenType === TOKENTYPE.SYMBOL || state.token === 'in' && (0, _is.isConstantNode)(node) || state.tokenType === TOKENTYPE.NUMBER && !(0, _is.isConstantNode)(last) && (!(0, _is.isOperatorNode)(last) || last.op === '!') || state.token === '(') { // parse implicit multiplication // // symbol: implicit multiplication like '2a', '(2+3)a', 'a b' // number: implicit multiplication like '(2+3)2' // parenthesis: implicit multiplication like '2(3+4)', '(3+4)(1+2)' last = parseRule2(state); node = new OperatorNode('*', 'multiply', [node, last], true /* implicit */ ); } else { break; } } return node; } /** * Infamous "rule 2" as described in https://github.com/josdejong/mathjs/issues/792#issuecomment-361065370 * Explicit division gets higher precedence than implicit multiplication * when the division matches this pattern: [number] / [number] [symbol] * @return {Node} node * @private */ function parseRule2(state) { var node = parsePercentage(state); var last = node; var tokenStates = []; while (true) { // Match the "number /" part of the pattern "number / number symbol" if (state.token === '/' && (0, _is.isConstantNode)(last)) { // Look ahead to see if the next token is a number tokenStates.push((0, _extends2.default)({}, state)); getTokenSkipNewline(state); // Match the "number / number" part of the pattern if (state.tokenType === TOKENTYPE.NUMBER) { // Look ahead again tokenStates.push((0, _extends2.default)({}, state)); getTokenSkipNewline(state); // Match the "symbol" part of the pattern, or a left parenthesis if (state.tokenType === TOKENTYPE.SYMBOL || state.token === '(') { // We've matched the pattern "number / number symbol". // Rewind once and build the "number / number" node; the symbol will be consumed later (0, _extends2.default)(state, tokenStates.pop()); tokenStates.pop(); last = parsePercentage(state); node = new OperatorNode('/', 'divide', [node, last]); } else { // Not a match, so rewind tokenStates.pop(); (0, _extends2.default)(state, tokenStates.pop()); break; } } else { // Not a match, so rewind (0, _extends2.default)(state, tokenStates.pop()); break; } } else { break; } } return node; } /** * percentage or mod * @return {Node} node * @private */ function parsePercentage(state) { var node, name, fn, params; node = parseUnary(state); var operators = { '%': 'mod', mod: 'mod' }; while ((0, _object.hasOwnProperty)(operators, state.token)) { name = state.token; fn = operators[name]; getTokenSkipNewline(state); if (name === '%' && state.tokenType === TOKENTYPE.DELIMITER && state.token !== '(') { // If the expression contains only %, then treat that as /100 node = new OperatorNode('/', 'divide', [node, new ConstantNode(100)], false, true); } else { params = [node, parseUnary(state)]; node = new OperatorNode(name, fn, params); } } return node; } /** * Unary plus and minus, and logical and bitwise not * @return {Node} node * @private */ function parseUnary(state) { var name, params, fn; var operators = { '-': 'unaryMinus', '+': 'unaryPlus', '~': 'bitNot', not: 'not' }; if ((0, _object.hasOwnProperty)(operators, state.token)) { fn = operators[state.token]; name = state.token; getTokenSkipNewline(state); params = [parseUnary(state)]; return new OperatorNode(name, fn, params); } return parsePow(state); } /** * power * Note: power operator is right associative * @return {Node} node * @private */ function parsePow(state) { var node, name, fn, params; node = parseLeftHandOperators(state); if (state.token === '^' || state.token === '.^') { name = state.token; fn = name === '^' ? 'pow' : 'dotPow'; getTokenSkipNewline(state); params = [node, parseUnary(state)]; // Go back to unary, we can have '2^-3' node = new OperatorNode(name, fn, params); } return node; } /** * Left hand operators: factorial x!, ctranspose x' * @return {Node} node * @private */ function parseLeftHandOperators(state) { var node, name, fn, params; node = parseCustomNodes(state); var operators = { '!': 'factorial', '\'': 'ctranspose' }; while ((0, _object.hasOwnProperty)(operators, state.token)) { name = state.token; fn = operators[name]; getToken(state); params = [node]; node = new OperatorNode(name, fn, params); node = parseAccessors(state, node); } return node; } /** * Parse a custom node handler. A node handler can be used to process * nodes in a custom way, for example for handling a plot. * * A handler must be passed as second argument of the parse function. * - must extend math.Node * - must contain a function _compile(defs: Object) : string * - must contain a function find(filter: Object) : Node[] * - must contain a function toString() : string * - the constructor is called with a single argument containing all parameters * * For example: * * nodes = { * 'plot': PlotHandler * } * * The constructor of the handler is called as: * * node = new PlotHandler(params) * * The handler will be invoked when evaluating an expression like: * * node = math.parse('plot(sin(x), x)', nodes) * * @return {Node} node * @private */ function parseCustomNodes(state) { var params = []; if (state.tokenType === TOKENTYPE.SYMBOL && (0, _object.hasOwnProperty)(state.extraNodes, state.token)) { var CustomNode = state.extraNodes[state.token]; getToken(state); // parse parameters if (state.token === '(') { params = []; openParams(state); getToken(state); if (state.token !== ')') { params.push(parseAssignment(state)); // parse a list with parameters while (state.token === ',') { // eslint-disable-line no-unmodified-loop-condition getToken(state); params.push(parseAssignment(state)); } } if (state.token !== ')') { throw createSyntaxError(state, 'Parenthesis ) expected'); } closeParams(state); getToken(state); } // create a new custom node // noinspection JSValidateTypes return new CustomNode(params); } return parseSymbol(state); } /** * parse symbols: functions, variables, constants, units * @return {Node} node * @private */ function parseSymbol(state) { var node, name; if (state.tokenType === TOKENTYPE.SYMBOL || state.tokenType === TOKENTYPE.DELIMITER && state.token in NAMED_DELIMITERS) { name = state.token; getToken(state); if ((0, _object.hasOwnProperty)(CONSTANTS, name)) { // true, false, null, ... node = new ConstantNode(CONSTANTS[name]); } else if (NUMERIC_CONSTANTS.indexOf(name) !== -1) { // NaN, Infinity node = new ConstantNode(numeric(name, 'number')); } else { node = new SymbolNode(name); } // parse function parameters and matrix index node = parseAccessors(state, node); return node; } return parseDoubleQuotesString(state); } /** * parse accessors: * - function invocation in round brackets (...), for example sqrt(2) * - index enclosed in square brackets [...], for example A[2,3] * - dot notation for properties, like foo.bar * @param {Object} state * @param {Node} node Node on which to apply the parameters. If there * are no parameters in the expression, the node * itself is returned * @param {string[]} [types] Filter the types of notations * can be ['(', '[', '.'] * @return {Node} node * @private */ function parseAccessors(state, node, types) { var params; while ((state.token === '(' || state.token === '[' || state.token === '.') && (!types || types.indexOf(state.token) !== -1)) { // eslint-disable-line no-unmodified-loop-condition params = []; if (state.token === '(') { if ((0, _is.isSymbolNode)(node) || (0, _is.isAccessorNode)(node)) { // function invocation like fn(2, 3) or obj.fn(2, 3) openParams(state); getToken(state); if (state.token !== ')') { params.push(parseAssignment(state)); // parse a list with parameters while (state.token === ',') { // eslint-disable-line no-unmodified-loop-condition getToken(state); params.push(parseAssignment(state)); } } if (state.token !== ')') { throw createSyntaxError(state, 'Parenthesis ) expected'); } closeParams(state); getToken(state); node = new FunctionNode(node, params); } else { // implicit multiplication like (2+3)(4+5) or sqrt(2)(1+2) // don't parse it here but let it be handled by parseImplicitMultiplication // with correct precedence return node; } } else if (state.token === '[') { // index notation like variable[2, 3] openParams(state); getToken(state); if (state.token !== ']') { params.push(parseAssignment(state)); // parse a list with parameters while (state.token === ',') { // eslint-disable-line no-unmodified-loop-condition getToken(state); params.push(parseAssignment(state)); } } if (state.token !== ']') { throw createSyntaxError(state, 'Parenthesis ] expected'); } closeParams(state); getToken(state); node = new AccessorNode(node, new IndexNode(params)); } else { // dot notation like variable.prop getToken(state); if (state.tokenType !== TOKENTYPE.SYMBOL) { throw createSyntaxError(state, 'Property name expected after dot'); } params.push(new ConstantNode(state.token)); getToken(state); var dotNotation = true; node = new AccessorNode(node, new IndexNode(params, dotNotation)); } } return node; } /** * Parse a double quotes string. * @return {Node} node * @private */ function parseDoubleQuotesString(state) { var node, str; if (state.token === '"') { str = parseDoubleQuotesStringToken(state); // create constant node = new ConstantNode(str); // parse index parameters node = parseAccessors(state, node); return node; } return parseSingleQuotesString(state); } /** * Parse a string surrounded by double quotes "..." * @return {string} */ function parseDoubleQuotesStringToken(state) { var str = ''; while (currentCharacter(state) !== '' && currentCharacter(state) !== '"') { if (currentCharacter(state) === '\\') { // escape character, immediately process the next // character to prevent stopping at a next '\"' str += currentCharacter(state); next(state); } str += currentCharacter(state); next(state); } getToken(state); if (state.token !== '"') { throw createSyntaxError(state, 'End of string " expected'); } getToken(state); return JSON.parse('"' + str + '"'); // unescape escaped characters } /** * Parse a single quotes string. * @return {Node} node * @private */ function parseSingleQuotesString(state) { var node, str; if (state.token === '\'') { str = parseSingleQuotesStringToken(state); // create constant node = new ConstantNode(str); // parse index parameters node = parseAccessors(state, node); return node; } return parseMatrix(state); } /** * Parse a string surrounded by single quotes '...' * @return {string} */ function parseSingleQuotesStringToken(state) { var str = ''; while (currentCharacter(state) !== '' && currentCharacter(state) !== '\'') { if (currentCharacter(state) === '\\') { // escape character, immediately process the next // character to prevent stopping at a next '\'' str += currentCharacter(state); next(state); } str += currentCharacter(state); next(state); } getToken(state); if (state.token !== '\'') { throw createSyntaxError(state, 'End of string \' expected'); } getToken(state); return JSON.parse('"' + str + '"'); // unescape escaped characters } /** * parse the matrix * @return {Node} node * @private */ function parseMatrix(state) { var array, params, rows, cols; if (state.token === '[') { // matrix [...] openParams(state); getToken(state); if (state.token !== ']') { // this is a non-empty matrix var row = parseRow(state); if (state.token === ';') { // 2 dimensional array rows = 1; params = [row]; // the rows of the matrix are separated by dot-comma's while (state.token === ';') { // eslint-disable-line no-unmodified-loop-condition getToken(state); params[rows] = parseRow(state); rows++; } if (state.token !== ']') { throw createSyntaxError(state, 'End of matrix ] expected'); } closeParams(state); getToken(state); // check if the number of columns matches in all rows cols = params[0].items.length; for (var r = 1; r < rows; r++) { if (params[r].items.length !== cols) { throw createError(state, 'Column dimensions mismatch ' + '(' + params[r].items.length + ' !== ' + cols + ')'); } } array = new ArrayNode(params); } else { // 1 dimensional vector if (state.token !== ']') { throw createSyntaxError(state, 'End of matrix ] expected'); } closeParams(state); getToken(state); array = row; } } else { // this is an empty matrix "[ ]" closeParams(state); getToken(state); array = new ArrayNode([]); } return parseAccessors(state, array); } return parseObject(state); } /** * Parse a single comma-separated row from a matrix, like 'a, b, c' * @return {ArrayNode} node */ function parseRow(state) { var params = [parseAssignment(state)]; var len = 1; while (state.token === ',') { // eslint-disable-line no-unmodified-loop-condition getToken(state); // parse expression params[len] = parseAssignment(state); len++; } return new ArrayNode(params); } /** * parse an object, enclosed in angle brackets{...}, for example {value: 2} * @return {Node} node * @private */ function parseObject(state) { if (state.token === '{') { openParams(state); var key; var properties = {}; do { getToken(state); if (state.token !== '}') { // parse key if (state.token === '"') { key = parseDoubleQuotesStringToken(state); } else if (state.token === '\'') { key = parseSingleQuotesStringToken(state); } else if (state.tokenType === TOKENTYPE.SYMBOL || state.tokenType === TOKENTYPE.DELIMITER && state.token in NAMED_DELIMITERS) { key = state.token; getToken(state); } else { throw createSyntaxError(state, 'Symbol or string expected as object key'); } // parse key/value separator if (state.token !== ':') { throw createSyntaxError(state, 'Colon : expected after object key'); } getToken(state); // parse key properties[key] = parseAssignment(state); } } while (state.token === ','); // eslint-disable-line no-unmodified-loop-condition if (state.token !== '}') { throw createSyntaxError(state, 'Comma , or bracket } expected after object value'); } closeParams(state); getToken(state); var node = new ObjectNode(properties); // parse index parameters node = parseAccessors(state, node); return node; } return parseNumber(state); } /** * parse a number * @return {Node} node * @private */ function parseNumber(state) { var numberStr; if (state.tokenType === TOKENTYPE.NUMBER) { // this is a number numberStr = state.token; getToken(state); return new ConstantNode(numeric(numberStr, config.number)); } return parseParentheses(state); } /** * parentheses * @return {Node} node * @private */ function parseParentheses(state) { var node; // check if it is a parenthesized expression if (state.token === '(') { // parentheses (...) openParams(state); getToken(state); node = parseAssignment(state); // start again if (state.token !== ')') { throw createSyntaxError(state, 'Parenthesis ) expected'); } closeParams(state); getToken(state); node = new ParenthesisNode(node); node = parseAccessors(state, node); return node; } return parseEnd(state); } /** * Evaluated when the expression is not yet ended but expected to end * @return {Node} res * @private */ function parseEnd(state) { if (state.token === '') { // syntax error or unexpected end of expression throw createSyntaxError(state, 'Unexpected end of expression'); } else { throw createSyntaxError(state, 'Value expected'); } } /** * Shortcut for getting the current row value (one based) * Returns the line of the currently handled expression * @private */ /* TODO: implement keeping track on the row number function row () { return null } */ /** * Shortcut for getting the current col value (one based) * Returns the column (position) where the last state.token starts * @private */ function col(state) { return state.index - state.token.length + 1; } /** * Create an error * @param {Object} state * @param {string} message * @return {SyntaxError} instantiated error * @private */ function createSyntaxError(state, message) { var c = col(state); var error = new SyntaxError(message + ' (char ' + c + ')'); error.char = c; return error; } /** * Create an error * @param {Object} state * @param {string} message * @return {Error} instantiated error * @private */ function createError(state, message) { var c = col(state); var error = new SyntaxError(message + ' (char ' + c + ')'); error.char = c; return error; } return parse; }); exports.createParse = createParse;