mathjs/lib/expression/parse.js
Jack Schmidt e1f0791574 Add complex transpose. A' now takes conjugate.
In matlab and octave the expression A' produces the Hermitian
conjugate, the complex conjugate of the transpose.

Now transpose produces the transpose, while ctranspose produces
the conjugate transpose. These are equal for real numbers, while
for complex numbers only the conjugate transpose is of much use.
2018-05-02 10:14:10 -04:00

1693 lines
40 KiB
JavaScript

'use strict';
var ArgumentsError = require('../error/ArgumentsError');
var deepMap = require('../utils/collection/deepMap');
function factory (type, config, load, typed) {
var numeric = load(require('../type/numeric'));
var AccessorNode = load(require('./node/AccessorNode'));
var ArrayNode = load(require('./node/ArrayNode'));
var AssignmentNode = load(require('./node/AssignmentNode'));
var BlockNode = load(require('./node/BlockNode'));
var ConditionalNode = load(require('./node/ConditionalNode'));
var ConstantNode = load(require('./node/ConstantNode'));
var FunctionAssignmentNode = load(require('./node/FunctionAssignmentNode'));
var IndexNode = load(require('./node/IndexNode'));
var ObjectNode = load(require('./node/ObjectNode'));
var OperatorNode = load(require('./node/OperatorNode'));
var ParenthesisNode = load(require('./node/ParenthesisNode'));
var FunctionNode = load(require('./node/FunctionNode'));
var RangeNode = load(require('./node/RangeNode'));
var SymbolNode = load(require('./node/SymbolNode'));
/**
* Parse an expression. Returns a node tree, which can be evaluated by
* invoking node.eval();
*
* Syntax:
*
* parse(expr)
* parse(expr, options)
* parse([expr1, expr2, expr3, ...])
* parse([expr1, expr2, expr3, ...], options)
*
* Example:
*
* var node = parse('sqrt(3^2 + 4^2)');
* node.compile(math).eval(); // 5
*
* var scope = {a:3, b:4}
* var node = parse('a * b'); // 12
* var code = node.compile(math);
* code.eval(scope); // 12
* scope.a = 5;
* code.eval(scope); // 20
*
* var nodes = math.parse(['a = 3', 'b = 4', 'a * b']);
* nodes[2].compile(math).eval(); // 12
*
* @param {string | string[] | Matrix} expr
* @param {{nodes: Object<string, Node>}} [options] Available options:
* - `nodes` a set of custom nodes
* @return {Node | Node[]} node
* @throws {Error}
*/
function parse (expr, options) {
if (arguments.length !== 1 && arguments.length !== 2) {
throw new ArgumentsError('parse', arguments.length, 1, 2);
}
// pass extra nodes
extra_nodes = (options && options.nodes) ? options.nodes : {};
if (typeof expr === 'string') {
// parse a single expression
expression = expr;
return parseStart();
}
else if (Array.isArray(expr) || expr instanceof type.Matrix) {
// parse an array or matrix with expressions
return deepMap(expr, function (elem) {
if (typeof elem !== 'string') throw new TypeError('String expected');
expression = elem;
return parseStart();
});
}
else {
// oops
throw new TypeError('String or matrix expected');
}
}
// 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'
]
var extra_nodes = {}; // current extra nodes
var expression = ''; // current expression
var comment = ''; // last parsed comment
var index = 0; // current index in expr
var c = ''; // current token character in expr
var token = ''; // current token
var token_type = TOKENTYPE.NULL; // type of the token
var nesting_level = 0; // level of nesting inside parameters, used to ignore newline characters
var conditional_level = null; // when a conditional is being parsed, the level of the conditional is stored here
var tokenStates = []; // holds saved token states
/**
* Get the first 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 first() {
index = 0;
c = expression.charAt(0);
nesting_level = 0;
conditional_level = null;
}
/**
* 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() {
index++;
c = expression.charAt(index);
}
/**
* Preview the previous character from the expression.
* @return {string} cNext
* @private
*/
function prevPreview() {
return expression.charAt(index - 1);
}
/**
* Preview the next character from the expression.
* @return {string} cNext
* @private
*/
function nextPreview() {
return expression.charAt(index + 1);
}
/**
* Preview the second next character from the expression.
* @return {string} cNext
* @private
*/
function nextNextPreview() {
return expression.charAt(index + 2);
}
/**
* Save the current token state so we can rewind later if necessary.
* @private
*/
function pushTokenState() {
tokenStates.push({
token_type: token_type,
token: token,
comment: comment,
index: index,
c: c
});
}
/**
* Rewind the parser by one token by restoring the last saved token state
* @private
*/
function popTokenState() {
var restoredState = tokenStates.pop();
token_type = restoredState.token_type;
token = restoredState.token;
comment = restoredState.comment;
index = restoredState.index;
c = restoredState.c;
}
/**
* Discard the most recent token state without restoring it
* @private
*/
function discardTokenState() {
tokenStates.pop();
}
/**
* Get next token in the current string expr.
* The token and token type are available as token and token_type
* @private
*/
function getToken() {
token_type = TOKENTYPE.NULL;
token = '';
comment = '';
// skip over whitespaces
// space, tab, and newline when inside parameters
while (parse.isWhitespace(c, nesting_level)) {
next();
}
// skip comment
if (c === '#') {
while (c !== '\n' && c !== '') {
comment += c;
next();
}
}
// check for end of expression
if (c === '') {
// token is still empty
token_type = TOKENTYPE.DELIMITER;
return;
}
// check for new line character
if (c === '\n' && !nesting_level) {
token_type = TOKENTYPE.DELIMITER;
token = c;
next();
return;
}
// check for delimiters consisting of 3 characters
var c2 = c + nextPreview();
var c3 = c2 + nextNextPreview();
if (c3.length === 3 && DELIMITERS[c3]) {
token_type = TOKENTYPE.DELIMITER;
token = c3;
next();
next();
next();
return;
}
// check for delimiters consisting of 2 characters
if (c2.length === 2 && DELIMITERS[c2]) {
token_type = TOKENTYPE.DELIMITER;
token = c2;
next();
next();
return;
}
// check for delimiters consisting of 1 character
if (DELIMITERS[c]) {
token_type = TOKENTYPE.DELIMITER;
token = c;
next();
return;
}
// check for a number
if (parse.isDigitDot(c)) {
token_type = TOKENTYPE.NUMBER;
// get number, can have a single dot
if (c === '.') {
token += c;
next();
if (!parse.isDigit(c)) {
// this is no number, it is just a dot (can be dot notation)
token_type = TOKENTYPE.DELIMITER;
}
}
else {
while (parse.isDigit(c)) {
token += c;
next();
}
if (parse.isDecimalMark(c, nextPreview())) {
token += c;
next();
}
}
while (parse.isDigit(c)) {
token += c;
next();
}
// check for exponential notation like "2.3e-4", "1.23e50" or "2e+4"
c2 = nextPreview();
if (c === 'E' || c === 'e') {
if (parse.isDigit(c2) || c2 === '-' || c2 === '+') {
token += c;
next();
if (c === '+' || c === '-') {
token += c;
next();
}
// Scientific notation MUST be followed by an exponent
if (!parse.isDigit(c)) {
throw createSyntaxError('Digit expected, got "' + c + '"');
}
while (parse.isDigit(c)) {
token += c;
next();
}
if (parse.isDecimalMark(c, nextPreview())) {
throw createSyntaxError('Digit expected, got "' + c + '"');
}
}
else if (c2 === '.') {
next();
throw createSyntaxError('Digit expected, got "' + c + '"');
}
}
return;
}
// check for variables, functions, named operators
if (parse.isAlpha(c, prevPreview(), nextPreview())) {
while (parse.isAlpha(c, prevPreview(), nextPreview()) || parse.isDigit(c)) {
token += c;
next();
}
if (NAMED_DELIMITERS.hasOwnProperty(token)) {
token_type = TOKENTYPE.DELIMITER;
}
else {
token_type = TOKENTYPE.SYMBOL;
}
return;
}
// something unknown is found, wrong characters -> a syntax error
token_type = TOKENTYPE.UNKNOWN;
while (c !== '') {
token += c;
next();
}
throw createSyntaxError('Syntax error in part "' + token + '"');
}
/**
* Get next token and skip newline tokens
*/
function getTokenSkipNewline () {
do {
getToken();
}
while (token === '\n');
}
/**
* Open parameters.
* New line characters will be ignored until closeParams() is called
*/
function openParams() {
nesting_level++;
}
/**
* Close parameters.
* New line characters will no longer be ignored
*/
function closeParams() {
nesting_level--;
}
/**
* 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.
*
* http://unicode-table.com/en/
* http://www.wikiwand.com/en/Mathematical_operators_and_symbols_in_Unicode
*
* Note: In ES6 will be unicode aware:
* http://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');
};
/**
* Start of the parse levels below, in order of precedence
* @return {Node} node
* @private
*/
function parseStart () {
// get the first character in expression
first();
getToken();
var node = parseBlock();
// check for garbage at the end of the expression
// an expression ends with a empty character '' and token_type DELIMITER
if (token !== '') {
if (token_type === 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('Unexpected operator ' + token);
}
else {
throw createSyntaxError('Unexpected part "' + 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 () {
var node;
var blocks = [];
var visible;
if (token !== '' && token !== '\n' && token !== ';') {
node = parseAssignment();
node.comment = comment;
}
// TODO: simplify this loop
while (token === '\n' || token === ';') {
if (blocks.length === 0 && node) {
visible = (token !== ';');
blocks.push({
node: node,
visible: visible
});
}
getToken();
if (token !== '\n' && token !== ';' && token !== '') {
node = parseAssignment();
node.comment = comment;
visible = (token !== ';');
blocks.push({
node: node,
visible: visible
});
}
}
if (blocks.length > 0) {
return new BlockNode(blocks);
}
else {
if (!node) {
node = new ConstantNode(undefined);
node.comment = 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 () {
var name, args, value, valid;
var node = parseConditional();
if (token === '=') {
if (type.isSymbolNode(node)) {
// parse a variable assignment like 'a = 2/3'
name = node.name;
getTokenSkipNewline();
value = parseAssignment();
return new AssignmentNode(new SymbolNode(name), value);
}
else if (type.isAccessorNode(node)) {
// parse a matrix subset assignment like 'A[1,2] = 4'
getTokenSkipNewline();
value = parseAssignment();
return new AssignmentNode(node.object, node.index, value);
}
else if (type.isFunctionNode(node) && type.isSymbolNode(node.fn)) {
// parse function assignment like 'f(x) = x^2'
valid = true;
args = [];
name = node.name;
node.args.forEach(function (arg, index) {
if (type.isSymbolNode(arg)) {
args[index] = arg.name;
}
else {
valid = false;
}
});
if (valid) {
getTokenSkipNewline();
value = parseAssignment();
return new FunctionAssignmentNode(name, args, value);
}
}
throw createSyntaxError('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 () {
var node = parseLogicalOr();
while (token === '?') {
// set a conditional level, the range operator will be ignored as long
// as conditional_level === nesting_level.
var prev = conditional_level;
conditional_level = nesting_level;
getTokenSkipNewline();
var condition = node;
var trueExpr = parseAssignment();
if (token !== ':') throw createSyntaxError('False part of conditional expression expected');
conditional_level = null;
getTokenSkipNewline();
var falseExpr = parseAssignment(); // Note: check for conditional operator again, right associativity
node = new ConditionalNode(condition, trueExpr, falseExpr);
// restore the previous conditional level
conditional_level = prev;
}
return node;
}
/**
* logical or, 'x or y'
* @return {Node} node
* @private
*/
function parseLogicalOr() {
var node = parseLogicalXor();
while (token === 'or') {
getTokenSkipNewline();
node = new OperatorNode('or', 'or', [node, parseLogicalXor()]);
}
return node;
}
/**
* logical exclusive or, 'x xor y'
* @return {Node} node
* @private
*/
function parseLogicalXor() {
var node = parseLogicalAnd();
while (token === 'xor') {
getTokenSkipNewline();
node = new OperatorNode('xor', 'xor', [node, parseLogicalAnd()]);
}
return node;
}
/**
* logical and, 'x and y'
* @return {Node} node
* @private
*/
function parseLogicalAnd() {
var node = parseBitwiseOr();
while (token === 'and') {
getTokenSkipNewline();
node = new OperatorNode('and', 'and', [node, parseBitwiseOr()]);
}
return node;
}
/**
* bitwise or, 'x | y'
* @return {Node} node
* @private
*/
function parseBitwiseOr() {
var node = parseBitwiseXor();
while (token === '|') {
getTokenSkipNewline();
node = new OperatorNode('|', 'bitOr', [node, parseBitwiseXor()]);
}
return node;
}
/**
* bitwise exclusive or (xor), 'x ^| y'
* @return {Node} node
* @private
*/
function parseBitwiseXor() {
var node = parseBitwiseAnd();
while (token === '^|') {
getTokenSkipNewline();
node = new OperatorNode('^|', 'bitXor', [node, parseBitwiseAnd()]);
}
return node;
}
/**
* bitwise and, 'x & y'
* @return {Node} node
* @private
*/
function parseBitwiseAnd () {
var node = parseRelational();
while (token === '&') {
getTokenSkipNewline();
node = new OperatorNode('&', 'bitAnd', [node, parseRelational()]);
}
return node;
}
/**
* relational operators
* @return {Node} node
* @private
*/
function parseRelational () {
var node, operators, name, fn, params;
node = parseShift();
operators = {
'==': 'equal',
'!=': 'unequal',
'<': 'smaller',
'>': 'larger',
'<=': 'smallerEq',
'>=': 'largerEq'
};
while (operators.hasOwnProperty(token)) {
name = token;
fn = operators[name];
getTokenSkipNewline();
params = [node, parseShift()];
node = new OperatorNode(name, fn, params);
}
return node;
}
/**
* Bitwise left shift, bitwise right arithmetic shift, bitwise right logical shift
* @return {Node} node
* @private
*/
function parseShift () {
var node, operators, name, fn, params;
node = parseConversion();
operators = {
'<<' : 'leftShift',
'>>' : 'rightArithShift',
'>>>' : 'rightLogShift'
};
while (operators.hasOwnProperty(token)) {
name = token;
fn = operators[name];
getTokenSkipNewline();
params = [node, parseConversion()];
node = new OperatorNode(name, fn, params);
}
return node;
}
/**
* conversion operators 'to' and 'in'
* @return {Node} node
* @private
*/
function parseConversion () {
var node, operators, name, fn, params;
node = parseRange();
operators = {
'to' : 'to',
'in' : 'to' // alias of 'to'
};
while (operators.hasOwnProperty(token)) {
name = token;
fn = operators[name];
getTokenSkipNewline();
if (name === 'in' && 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()];
node = new OperatorNode(name, fn, params);
}
}
return node;
}
/**
* parse range, "start:end", "start:step:end", ":", "start:", ":end", etc
* @return {Node} node
* @private
*/
function parseRange () {
var node, params = [];
if (token === ':') {
// implicit start=1 (one-based)
node = new ConstantNode(1);
}
else {
// explicit start
node = parseAddSubtract();
}
if (token === ':' && (conditional_level !== nesting_level)) {
// we ignore the range operator when a conditional operator is being processed on the same level
params.push(node);
// parse step and end
while (token === ':' && params.length < 3) {
getTokenSkipNewline();
if (token === ')' || token === ']' || token === ',' || token === '') {
// implicit end
params.push(new SymbolNode('end'));
}
else {
// explicit end
params.push(parseAddSubtract());
}
}
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 () {
var node, operators, name, fn, params;
node = parseMultiplyDivide();
operators = {
'+': 'add',
'-': 'subtract'
};
while (operators.hasOwnProperty(token)) {
name = token;
fn = operators[name];
getTokenSkipNewline();
params = [node, parseMultiplyDivide()];
node = new OperatorNode(name, fn, params);
}
return node;
}
/**
* multiply, divide, modulus
* @return {Node} node
* @private
*/
function parseMultiplyDivide () {
var node, last, operators, name, fn;
node = parseImplicitMultiplication();
last = node;
operators = {
'*': 'multiply',
'.*': 'dotMultiply',
'/': 'divide',
'./': 'dotDivide',
'%': 'mod',
'mod': 'mod'
};
while (true) {
if (operators.hasOwnProperty(token)) {
// explicit operators
name = token;
fn = operators[name];
getTokenSkipNewline();
last = parseImplicitMultiplication();
node = new OperatorNode(name, fn, [node, last]);
}
else {
break;
}
}
return node;
}
/**
* implicit multiplication
* @return {Node} node
* @private
*/
function parseImplicitMultiplication () {
var node, last;
node = parseRule2();
last = node;
while (true) {
if ((token_type === TOKENTYPE.SYMBOL) ||
(token === 'in' && type.isConstantNode(node)) ||
(token_type === TOKENTYPE.NUMBER &&
!type.isConstantNode(last) &&
(!type.isOperatorNode(last) || last.op === '!')) ||
(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();
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 () {
var node, last;
node = parseUnary();
last = node;
while(true) {
// Match the "number /" part of the pattern "number / number symbol"
if(token === '/' && type.isConstantNode(last)) {
// Look ahead to see if the next token is a number
pushTokenState();
getTokenSkipNewline();
// Match the "number / number" part of the pattern
if(token_type === TOKENTYPE.NUMBER) {
// Look ahead again
pushTokenState();
getTokenSkipNewline();
// Match the "symbol" part of the pattern, or a left parenthesis
if(token_type === TOKENTYPE.SYMBOL || token === '(') {
// We've matched the pattern "number / number symbol".
// Rewind once and build the "number / number" node; the symbol will be consumed later
popTokenState();
discardTokenState();
last = parseUnary();
node = new OperatorNode('/', 'divide', [node, last]);
}
else {
// Not a match, so rewind
popTokenState();
popTokenState();
break;
}
}
else {
// Not a match, so rewind
popTokenState();
break;
}
}
else {
break;
}
}
return node;
}
/**
* Unary plus and minus, and logical and bitwise not
* @return {Node} node
* @private
*/
function parseUnary () {
var name, params, fn;
var operators = {
'-': 'unaryMinus',
'+': 'unaryPlus',
'~': 'bitNot',
'not': 'not'
};
if (operators.hasOwnProperty(token)) {
fn = operators[token];
name = token;
getTokenSkipNewline();
params = [parseUnary()];
return new OperatorNode(name, fn, params);
}
return parsePow();
}
/**
* power
* Note: power operator is right associative
* @return {Node} node
* @private
*/
function parsePow () {
var node, name, fn, params;
node = parseLeftHandOperators();
if (token === '^' || token === '.^') {
name = token;
fn = (name === '^') ? 'pow' : 'dotPow';
getTokenSkipNewline();
params = [node, parseUnary()]; // 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 () {
var node, operators, name, fn, params;
node = parseCustomNodes();
operators = {
'!': 'factorial',
'\'': 'ctranspose'
};
while (operators.hasOwnProperty(token)) {
name = token;
fn = operators[name];
getToken();
params = [node];
node = new OperatorNode(name, fn, params);
node = parseAccessors(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.expression.node.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 () {
var params = [];
if (token_type === TOKENTYPE.SYMBOL && extra_nodes.hasOwnProperty(token)) {
var CustomNode = extra_nodes[token];
getToken();
// parse parameters
if (token === '(') {
params = [];
openParams();
getToken();
if (token !== ')') {
params.push(parseAssignment());
// parse a list with parameters
while (token === ',') {
getToken();
params.push(parseAssignment());
}
}
if (token !== ')') {
throw createSyntaxError('Parenthesis ) expected');
}
closeParams();
getToken();
}
// create a new custom node
//noinspection JSValidateTypes
return new CustomNode(params);
}
return parseSymbol();
}
/**
* parse symbols: functions, variables, constants, units
* @return {Node} node
* @private
*/
function parseSymbol () {
var node, name;
if (token_type === TOKENTYPE.SYMBOL ||
(token_type === TOKENTYPE.DELIMITER && token in NAMED_DELIMITERS)) {
name = token;
getToken();
if (CONSTANTS.hasOwnProperty(name)) { // true, false, null, ...
node = new ConstantNode(CONSTANTS[name]);
}
else if (NUMERIC_CONSTANTS.indexOf(name) !== -1) { // NaN, Infinity
node = new ConstantNode(numeric(name));
}
else {
node = new SymbolNode(name);
}
// parse function parameters and matrix index
node = parseAccessors(node);
return node;
}
return parseString();
}
/**
* 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 {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 (node, types) {
var params;
while ((token === '(' || token === '[' || token === '.') &&
(!types || types.indexOf(token) !== -1)) {
params = [];
if (token === '(') {
if (type.isSymbolNode(node) || type.isAccessorNode(node)) {
// function invocation like fn(2, 3) or obj.fn(2, 3)
openParams();
getToken();
if (token !== ')') {
params.push(parseAssignment());
// parse a list with parameters
while (token === ',') {
getToken();
params.push(parseAssignment());
}
}
if (token !== ')') {
throw createSyntaxError('Parenthesis ) expected');
}
closeParams();
getToken();
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 (token === '[') {
// index notation like variable[2, 3]
openParams();
getToken();
if (token !== ']') {
params.push(parseAssignment());
// parse a list with parameters
while (token === ',') {
getToken();
params.push(parseAssignment());
}
}
if (token !== ']') {
throw createSyntaxError('Parenthesis ] expected');
}
closeParams();
getToken();
node = new AccessorNode(node, new IndexNode(params));
}
else {
// dot notation like variable.prop
getToken();
if (token_type !== TOKENTYPE.SYMBOL) {
throw createSyntaxError('Property name expected after dot');
}
params.push(new ConstantNode(token));
getToken();
var dotNotation = true;
node = new AccessorNode(node, new IndexNode(params, dotNotation));
}
}
return node;
}
/**
* parse a string.
* A string is enclosed by double quotes
* @return {Node} node
* @private
*/
function parseString () {
var node, str;
if (token === '"') {
str = parseStringToken();
// create constant
node = new ConstantNode(str);
// parse index parameters
node = parseAccessors(node);
return node;
}
return parseMatrix();
}
/**
* Parse a string surrounded by double quotes "..."
* @return {string}
*/
function parseStringToken () {
var str = '';
while (c !== '' && c !== '\"') {
if (c === '\\') {
// escape character, immediately process the next
// character to prevent stopping at a next '\"'
str += c;
next();
}
str += c;
next();
}
getToken();
if (token !== '"') {
throw createSyntaxError('End of string " expected');
}
getToken();
return JSON.parse('"' + str + '"'); // unescape escaped characters
}
/**
* parse the matrix
* @return {Node} node
* @private
*/
function parseMatrix () {
var array, params, rows, cols;
if (token === '[') {
// matrix [...]
openParams();
getToken();
if (token !== ']') {
// this is a non-empty matrix
var row = parseRow();
if (token === ';') {
// 2 dimensional array
rows = 1;
params = [row];
// the rows of the matrix are separated by dot-comma's
while (token === ';') {
getToken();
params[rows] = parseRow();
rows++;
}
if (token !== ']') {
throw createSyntaxError('End of matrix ] expected');
}
closeParams();
getToken();
// 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('Column dimensions mismatch ' +
'(' + params[r].items.length + ' !== ' + cols + ')');
}
}
array = new ArrayNode(params);
}
else {
// 1 dimensional vector
if (token !== ']') {
throw createSyntaxError('End of matrix ] expected');
}
closeParams();
getToken();
array = row;
}
}
else {
// this is an empty matrix "[ ]"
closeParams();
getToken();
array = new ArrayNode([]);
}
return parseAccessors(array);
}
return parseObject();
}
/**
* Parse a single comma-separated row from a matrix, like 'a, b, c'
* @return {ArrayNode} node
*/
function parseRow () {
var params = [parseAssignment()];
var len = 1;
while (token === ',') {
getToken();
// parse expression
params[len] = parseAssignment();
len++;
}
return new ArrayNode(params);
}
/**
* parse an object, enclosed in angle brackets{...}, for example {value: 2}
* @return {Node} node
* @private
*/
function parseObject () {
if (token === '{') {
var key;
var properties = {};
do {
getToken();
if (token !== '}') {
// parse key
if (token === '"') {
key = parseStringToken();
}
else if (token_type === TOKENTYPE.SYMBOL) {
key = token;
getToken();
}
else {
throw createSyntaxError('Symbol or string expected as object key');
}
// parse key/value separator
if (token !== ':') {
throw createSyntaxError('Colon : expected after object key');
}
getToken();
// parse key
properties[key] = parseAssignment();
}
}
while (token === ',');
if (token !== '}') {
throw createSyntaxError('Comma , or bracket } expected after object value');
}
getToken();
var node = new ObjectNode(properties);
// parse index parameters
node = parseAccessors(node);
return node;
}
return parseNumber();
}
/**
* parse a number
* @return {Node} node
* @private
*/
function parseNumber () {
var numberStr;
if (token_type === TOKENTYPE.NUMBER) {
// this is a number
numberStr = token;
getToken();
return new ConstantNode(numeric(numberStr, config.number));
}
return parseParentheses();
}
/**
* parentheses
* @return {Node} node
* @private
*/
function parseParentheses () {
var node;
// check if it is a parenthesized expression
if (token === '(') {
// parentheses (...)
openParams();
getToken();
node = parseAssignment(); // start again
if (token !== ')') {
throw createSyntaxError('Parenthesis ) expected');
}
closeParams();
getToken();
node = new ParenthesisNode(node);
node = parseAccessors(node);
return node;
}
return parseEnd();
}
/**
* Evaluated when the expression is not yet ended but expected to end
* @return {Node} res
* @private
*/
function parseEnd () {
if (token === '') {
// syntax error or unexpected end of expression
throw createSyntaxError('Unexpected end of expression');
} else if (token === "'") {
throw createSyntaxError('Value expected. Note: strings must be enclosed by double quotes');
} else {
throw createSyntaxError('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 token starts
* @private
*/
function col () {
return index - token.length + 1;
}
/**
* Create an error
* @param {string} message
* @return {SyntaxError} instantiated error
* @private
*/
function createSyntaxError (message) {
var c = col();
var error = new SyntaxError(message + ' (char ' + c + ')');
error['char'] = c;
return error;
}
/**
* Create an error
* @param {string} message
* @return {Error} instantiated error
* @private
*/
function createError (message) {
var c = col();
var error = new SyntaxError(message + ' (char ' + c + ')');
error['char'] = c;
return error;
}
return parse;
}
exports.name = 'parse';
exports.path = 'expression';
exports.factory = factory;