simple-squiggle/node_modules/mathjs/lib/cjs/expression/parse.js

1811 lines
48 KiB
JavaScript

"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;