text: "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<string, Node>}} [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;