UNPKG

45.2 kBJavaScriptView Raw
1'use strict';
2
3var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
4
5var ArgumentsError = require('../error/ArgumentsError');
6var deepMap = require('../utils/collection/deepMap');
7
8function factory(type, config, load, typed) {
9 var numeric = load(require('../type/numeric'));
10
11 var AccessorNode = load(require('./node/AccessorNode'));
12 var ArrayNode = load(require('./node/ArrayNode'));
13 var AssignmentNode = load(require('./node/AssignmentNode'));
14 var BlockNode = load(require('./node/BlockNode'));
15 var ConditionalNode = load(require('./node/ConditionalNode'));
16 var ConstantNode = load(require('./node/ConstantNode'));
17 var FunctionAssignmentNode = load(require('./node/FunctionAssignmentNode'));
18 var IndexNode = load(require('./node/IndexNode'));
19 var ObjectNode = load(require('./node/ObjectNode'));
20 var OperatorNode = load(require('./node/OperatorNode'));
21 var ParenthesisNode = load(require('./node/ParenthesisNode'));
22 var FunctionNode = load(require('./node/FunctionNode'));
23 var RangeNode = load(require('./node/RangeNode'));
24 var SymbolNode = load(require('./node/SymbolNode'));
25
26 /**
27 * Parse an expression. Returns a node tree, which can be evaluated by
28 * invoking node.eval().
29 *
30 * Syntax:
31 *
32 * parse(expr)
33 * parse(expr, options)
34 * parse([expr1, expr2, expr3, ...])
35 * parse([expr1, expr2, expr3, ...], options)
36 *
37 * Example:
38 *
39 * const node = parse('sqrt(3^2 + 4^2)')
40 * node.compile(math).eval() // 5
41 *
42 * let scope = {a:3, b:4}
43 * const node = parse('a * b') // 12
44 * const code = node.compile(math)
45 * code.eval(scope) // 12
46 * scope.a = 5
47 * code.eval(scope) // 20
48 *
49 * const nodes = math.parse(['a = 3', 'b = 4', 'a * b'])
50 * nodes[2].compile(math).eval() // 12
51 *
52 * @param {string | string[] | Matrix} expr
53 * @param {{nodes: Object<string, Node>}} [options] Available options:
54 * - `nodes` a set of custom nodes
55 * @return {Node | Node[]} node
56 * @throws {Error}
57 */
58 function parse(expr, options) {
59 if (arguments.length !== 1 && arguments.length !== 2) {
60 throw new ArgumentsError('parse', arguments.length, 1, 2);
61 }
62
63 // pass extra nodes
64 var extraNodes = options && options.nodes ? options.nodes : {};
65
66 if (typeof expr === 'string') {
67 // parse a single expression
68
69 return parseStart(expr, extraNodes);
70 } else if (Array.isArray(expr) || expr instanceof type.Matrix) {
71 // parse an array or matrix with expressions
72 return deepMap(expr, function (elem) {
73 if (typeof elem !== 'string') throw new TypeError('String expected');
74
75 return parseStart(elem, extraNodes);
76 });
77 } else {
78 // oops
79 throw new TypeError('String or matrix expected');
80 }
81 }
82
83 // token types enumeration
84 var TOKENTYPE = {
85 NULL: 0,
86 DELIMITER: 1,
87 NUMBER: 2,
88 SYMBOL: 3,
89 UNKNOWN: 4
90
91 // map with all delimiters
92 };var DELIMITERS = {
93 ',': true,
94 '(': true,
95 ')': true,
96 '[': true,
97 ']': true,
98 '{': true,
99 '}': true,
100 '"': true,
101 ';': true,
102
103 '+': true,
104 '-': true,
105 '*': true,
106 '.*': true,
107 '/': true,
108 './': true,
109 '%': true,
110 '^': true,
111 '.^': true,
112 '~': true,
113 '!': true,
114 '&': true,
115 '|': true,
116 '^|': true,
117 '\'': true,
118 '=': true,
119 ':': true,
120 '?': true,
121
122 '==': true,
123 '!=': true,
124 '<': true,
125 '>': true,
126 '<=': true,
127 '>=': true,
128
129 '<<': true,
130 '>>': true,
131 '>>>': true
132
133 // map with all named delimiters
134 };var NAMED_DELIMITERS = {
135 'mod': true,
136 'to': true,
137 'in': true,
138 'and': true,
139 'xor': true,
140 'or': true,
141 'not': true
142 };
143
144 var CONSTANTS = {
145 'true': true,
146 'false': false,
147 'null': null,
148 'undefined': undefined
149 };
150
151 var NUMERIC_CONSTANTS = ['NaN', 'Infinity'];
152
153 function initialState() {
154 return {
155 extraNodes: {}, // current extra nodes, must be careful not to mutate
156 expression: '', // current expression
157 comment: '', // last parsed comment
158 index: 0, // current index in expr
159 token: '', // current token
160 tokenType: TOKENTYPE.NULL, // type of the token
161 nestingLevel: 0, // level of nesting inside parameters, used to ignore newline characters
162 conditionalLevel: null // when a conditional is being parsed, the level of the conditional is stored here
163 };
164 }
165
166 /**
167 * View upto `length` characters of the expression starting at the current character.
168 *
169 * @param {State} state
170 * @param {number} [length=1] Number of characters to view
171 * @returns {string}
172 * @private
173 */
174 function currentString(state, length) {
175 return state.expression.substr(state.index, length);
176 }
177
178 /**
179 * View the current character. Returns '' if end of expression is reached.
180 *
181 * @param {State} state
182 * @returns {string}
183 * @private
184 */
185 function currentCharacter(state) {
186 return currentString(state, 1);
187 }
188
189 /**
190 * Get the next character from the expression.
191 * The character is stored into the char c. If the end of the expression is
192 * reached, the function puts an empty string in c.
193 * @private
194 */
195 function next(state) {
196 state.index++;
197 }
198
199 /**
200 * Preview the previous character from the expression.
201 * @return {string} cNext
202 * @private
203 */
204 function prevCharacter(state) {
205 return state.expression.charAt(state.index - 1);
206 }
207
208 /**
209 * Preview the next character from the expression.
210 * @return {string} cNext
211 * @private
212 */
213 function nextCharacter(state) {
214 return state.expression.charAt(state.index + 1);
215 }
216
217 /**
218 * Get next token in the current string expr.
219 * The token and token type are available as token and tokenType
220 * @private
221 */
222 function getToken(state) {
223 state.tokenType = TOKENTYPE.NULL;
224 state.token = '';
225 state.comment = '';
226
227 // skip over whitespaces
228 // space, tab, and newline when inside parameters
229 while (parse.isWhitespace(currentCharacter(state), state.nestingLevel)) {
230 next(state);
231 }
232
233 // skip comment
234 if (currentCharacter(state) === '#') {
235 while (currentCharacter(state) !== '\n' && currentCharacter(state) !== '') {
236 state.comment += currentCharacter(state);
237 next(state);
238 }
239 }
240
241 // check for end of expression
242 if (currentCharacter(state) === '') {
243 // token is still empty
244 state.tokenType = TOKENTYPE.DELIMITER;
245 return;
246 }
247
248 // check for new line character
249 if (currentCharacter(state) === '\n' && !state.nestingLevel) {
250 state.tokenType = TOKENTYPE.DELIMITER;
251 state.token = currentCharacter(state);
252 next(state);
253 return;
254 }
255
256 var c1 = currentCharacter(state);
257 var c2 = currentString(state, 2);
258 var c3 = currentString(state, 3);
259 if (c3.length === 3 && DELIMITERS[c3]) {
260 state.tokenType = TOKENTYPE.DELIMITER;
261 state.token = c3;
262 next(state);
263 next(state);
264 next(state);
265 return;
266 }
267
268 // check for delimiters consisting of 2 characters
269 if (c2.length === 2 && DELIMITERS[c2]) {
270 state.tokenType = TOKENTYPE.DELIMITER;
271 state.token = c2;
272 next(state);
273 next(state);
274 return;
275 }
276
277 // check for delimiters consisting of 1 character
278 if (DELIMITERS[c1]) {
279 state.tokenType = TOKENTYPE.DELIMITER;
280 state.token = c1;
281 next(state);
282 return;
283 }
284
285 // check for a number
286 if (parse.isDigitDot(c1)) {
287 state.tokenType = TOKENTYPE.NUMBER;
288
289 // get number, can have a single dot
290 if (currentCharacter(state) === '.') {
291 state.token += currentCharacter(state);
292 next(state);
293
294 if (!parse.isDigit(currentCharacter(state))) {
295 // this is no number, it is just a dot (can be dot notation)
296 state.tokenType = TOKENTYPE.DELIMITER;
297 }
298 } else {
299 while (parse.isDigit(currentCharacter(state))) {
300 state.token += currentCharacter(state);
301 next(state);
302 }
303 if (parse.isDecimalMark(currentCharacter(state), nextCharacter(state))) {
304 state.token += currentCharacter(state);
305 next(state);
306 }
307 }
308
309 while (parse.isDigit(currentCharacter(state))) {
310 state.token += currentCharacter(state);
311 next(state);
312 }
313 // check for exponential notation like "2.3e-4", "1.23e50" or "2e+4"
314 if (currentCharacter(state) === 'E' || currentCharacter(state) === 'e') {
315 if (parse.isDigit(nextCharacter(state)) || nextCharacter(state) === '-' || nextCharacter(state) === '+') {
316 state.token += currentCharacter(state);
317 next(state);
318
319 if (currentCharacter(state) === '+' || currentCharacter(state) === '-') {
320 state.token += currentCharacter(state);
321 next(state);
322 }
323 // Scientific notation MUST be followed by an exponent
324 if (!parse.isDigit(currentCharacter(state))) {
325 throw createSyntaxError(state, 'Digit expected, got "' + currentCharacter(state) + '"');
326 }
327
328 while (parse.isDigit(currentCharacter(state))) {
329 state.token += currentCharacter(state);
330 next(state);
331 }
332
333 if (parse.isDecimalMark(currentCharacter(state), nextCharacter(state))) {
334 throw createSyntaxError(state, 'Digit expected, got "' + currentCharacter(state) + '"');
335 }
336 } else if (nextCharacter(state) === '.') {
337 next(state);
338 throw createSyntaxError(state, 'Digit expected, got "' + currentCharacter(state) + '"');
339 }
340 }
341
342 return;
343 }
344
345 // check for variables, functions, named operators
346 if (parse.isAlpha(currentCharacter(state), prevCharacter(state), nextCharacter(state))) {
347 while (parse.isAlpha(currentCharacter(state), prevCharacter(state), nextCharacter(state)) || parse.isDigit(currentCharacter(state))) {
348 state.token += currentCharacter(state);
349 next(state);
350 }
351
352 if (NAMED_DELIMITERS.hasOwnProperty(state.token)) {
353 state.tokenType = TOKENTYPE.DELIMITER;
354 } else {
355 state.tokenType = TOKENTYPE.SYMBOL;
356 }
357
358 return;
359 }
360
361 // something unknown is found, wrong characters -> a syntax error
362 state.tokenType = TOKENTYPE.UNKNOWN;
363 while (currentCharacter(state) !== '') {
364 state.token += currentCharacter(state);
365 next(state);
366 }
367 throw createSyntaxError(state, 'Syntax error in part "' + state.token + '"');
368 }
369
370 /**
371 * Get next token and skip newline tokens
372 */
373 function getTokenSkipNewline(state) {
374 do {
375 getToken(state);
376 } while (state.token === '\n'); // eslint-disable-line no-unmodified-loop-condition
377 }
378
379 /**
380 * Open parameters.
381 * New line characters will be ignored until closeParams(state) is called
382 */
383 function openParams(state) {
384 state.nestingLevel++;
385 }
386
387 /**
388 * Close parameters.
389 * New line characters will no longer be ignored
390 */
391 function closeParams(state) {
392 state.nestingLevel--;
393 }
394
395 /**
396 * Checks whether the current character `c` is a valid alpha character:
397 *
398 * - A latin letter (upper or lower case) Ascii: a-z, A-Z
399 * - An underscore Ascii: _
400 * - A dollar sign Ascii: $
401 * - A latin letter with accents Unicode: \u00C0 - \u02AF
402 * - A greek letter Unicode: \u0370 - \u03FF
403 * - A mathematical alphanumeric symbol Unicode: \u{1D400} - \u{1D7FF} excluding invalid code points
404 *
405 * The previous and next characters are needed to determine whether
406 * this character is part of a unicode surrogate pair.
407 *
408 * @param {string} c Current character in the expression
409 * @param {string} cPrev Previous character
410 * @param {string} cNext Next character
411 * @return {boolean}
412 */
413 parse.isAlpha = function isAlpha(c, cPrev, cNext) {
414 return parse.isValidLatinOrGreek(c) || parse.isValidMathSymbol(c, cNext) || parse.isValidMathSymbol(cPrev, c);
415 };
416
417 /**
418 * Test whether a character is a valid latin, greek, or letter-like character
419 * @param {string} c
420 * @return {boolean}
421 */
422 parse.isValidLatinOrGreek = function isValidLatinOrGreek(c) {
423 return (/^[a-zA-Z_$\u00C0-\u02AF\u0370-\u03FF\u2100-\u214F]$/.test(c)
424 );
425 };
426
427 /**
428 * Test whether two given 16 bit characters form a surrogate pair of a
429 * unicode math symbol.
430 *
431 * http://unicode-table.com/en/
432 * http://www.wikiwand.com/en/Mathematical_operators_and_symbols_in_Unicode
433 *
434 * Note: In ES6 will be unicode aware:
435 * http://stackoverflow.com/questions/280712/javascript-unicode-regexes
436 * https://mathiasbynens.be/notes/es6-unicode-regex
437 *
438 * @param {string} high
439 * @param {string} low
440 * @return {boolean}
441 */
442 parse.isValidMathSymbol = function isValidMathSymbol(high, low) {
443 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)
444 );
445 };
446
447 /**
448 * Check whether given character c is a white space character: space, tab, or enter
449 * @param {string} c
450 * @param {number} nestingLevel
451 * @return {boolean}
452 */
453 parse.isWhitespace = function isWhitespace(c, nestingLevel) {
454 // TODO: also take '\r' carriage return as newline? Or does that give problems on mac?
455 return c === ' ' || c === '\t' || c === '\n' && nestingLevel > 0;
456 };
457
458 /**
459 * Test whether the character c is a decimal mark (dot).
460 * This is the case when it's not the start of a delimiter '.*', './', or '.^'
461 * @param {string} c
462 * @param {string} cNext
463 * @return {boolean}
464 */
465 parse.isDecimalMark = function isDecimalMark(c, cNext) {
466 return c === '.' && cNext !== '/' && cNext !== '*' && cNext !== '^';
467 };
468
469 /**
470 * checks if the given char c is a digit or dot
471 * @param {string} c a string with one character
472 * @return {boolean}
473 */
474 parse.isDigitDot = function isDigitDot(c) {
475 return c >= '0' && c <= '9' || c === '.';
476 };
477
478 /**
479 * checks if the given char c is a digit
480 * @param {string} c a string with one character
481 * @return {boolean}
482 */
483 parse.isDigit = function isDigit(c) {
484 return c >= '0' && c <= '9';
485 };
486
487 /**
488 * Start of the parse levels below, in order of precedence
489 * @return {Node} node
490 * @private
491 */
492 function parseStart(expression, extraNodes) {
493 var state = initialState();
494 _extends(state, { expression: expression, extraNodes: extraNodes });
495 getToken(state);
496
497 var node = parseBlock(state);
498
499 // check for garbage at the end of the expression
500 // an expression ends with a empty character '' and tokenType DELIMITER
501 if (state.token !== '') {
502 if (state.tokenType === TOKENTYPE.DELIMITER) {
503 // user entered a not existing operator like "//"
504
505 // TODO: give hints for aliases, for example with "<>" give as hint " did you mean !== ?"
506 throw createError(state, 'Unexpected operator ' + state.token);
507 } else {
508 throw createSyntaxError(state, 'Unexpected part "' + state.token + '"');
509 }
510 }
511
512 return node;
513 }
514
515 /**
516 * Parse a block with expressions. Expressions can be separated by a newline
517 * character '\n', or by a semicolon ';'. In case of a semicolon, no output
518 * of the preceding line is returned.
519 * @return {Node} node
520 * @private
521 */
522 function parseBlock(state) {
523 var node = void 0;
524 var blocks = [];
525 var visible = void 0;
526
527 if (state.token !== '' && state.token !== '\n' && state.token !== ';') {
528 node = parseAssignment(state);
529 node.comment = state.comment;
530 }
531
532 // TODO: simplify this loop
533 while (state.token === '\n' || state.token === ';') {
534 // eslint-disable-line no-unmodified-loop-condition
535 if (blocks.length === 0 && node) {
536 visible = state.token !== ';';
537 blocks.push({
538 node: node,
539 visible: visible
540 });
541 }
542
543 getToken(state);
544 if (state.token !== '\n' && state.token !== ';' && state.token !== '') {
545 node = parseAssignment(state);
546 node.comment = state.comment;
547
548 visible = state.token !== ';';
549 blocks.push({
550 node: node,
551 visible: visible
552 });
553 }
554 }
555
556 if (blocks.length > 0) {
557 return new BlockNode(blocks);
558 } else {
559 if (!node) {
560 node = new ConstantNode(undefined);
561 node.comment = state.comment;
562 }
563
564 return node;
565 }
566 }
567
568 /**
569 * Assignment of a function or variable,
570 * - can be a variable like 'a=2.3'
571 * - or a updating an existing variable like 'matrix(2,3:5)=[6,7,8]'
572 * - defining a function like 'f(x) = x^2'
573 * @return {Node} node
574 * @private
575 */
576 function parseAssignment(state) {
577 var name = void 0,
578 args = void 0,
579 value = void 0,
580 valid = void 0;
581
582 var node = parseConditional(state);
583
584 if (state.token === '=') {
585 if (type.isSymbolNode(node)) {
586 // parse a variable assignment like 'a = 2/3'
587 name = node.name;
588 getTokenSkipNewline(state);
589 value = parseAssignment(state);
590 return new AssignmentNode(new SymbolNode(name), value);
591 } else if (type.isAccessorNode(node)) {
592 // parse a matrix subset assignment like 'A[1,2] = 4'
593 getTokenSkipNewline(state);
594 value = parseAssignment(state);
595 return new AssignmentNode(node.object, node.index, value);
596 } else if (type.isFunctionNode(node) && type.isSymbolNode(node.fn)) {
597 // parse function assignment like 'f(x) = x^2'
598 valid = true;
599 args = [];
600
601 name = node.name;
602 node.args.forEach(function (arg, index) {
603 if (type.isSymbolNode(arg)) {
604 args[index] = arg.name;
605 } else {
606 valid = false;
607 }
608 });
609
610 if (valid) {
611 getTokenSkipNewline(state);
612 value = parseAssignment(state);
613 return new FunctionAssignmentNode(name, args, value);
614 }
615 }
616
617 throw createSyntaxError(state, 'Invalid left hand side of assignment operator =');
618 }
619
620 return node;
621 }
622
623 /**
624 * conditional operation
625 *
626 * condition ? truePart : falsePart
627 *
628 * Note: conditional operator is right-associative
629 *
630 * @return {Node} node
631 * @private
632 */
633 function parseConditional(state) {
634 var node = parseLogicalOr(state);
635
636 while (state.token === '?') {
637 // eslint-disable-line no-unmodified-loop-condition
638 // set a conditional level, the range operator will be ignored as long
639 // as conditionalLevel === state.nestingLevel.
640 var prev = state.conditionalLevel;
641 state.conditionalLevel = state.nestingLevel;
642 getTokenSkipNewline(state);
643
644 var condition = node;
645 var trueExpr = parseAssignment(state);
646
647 if (state.token !== ':') throw createSyntaxError(state, 'False part of conditional expression expected');
648
649 state.conditionalLevel = null;
650 getTokenSkipNewline(state);
651
652 var falseExpr = parseAssignment(state); // Note: check for conditional operator again, right associativity
653
654 node = new ConditionalNode(condition, trueExpr, falseExpr);
655
656 // restore the previous conditional level
657 state.conditionalLevel = prev;
658 }
659
660 return node;
661 }
662
663 /**
664 * logical or, 'x or y'
665 * @return {Node} node
666 * @private
667 */
668 function parseLogicalOr(state) {
669 var node = parseLogicalXor(state);
670
671 while (state.token === 'or') {
672 // eslint-disable-line no-unmodified-loop-condition
673 getTokenSkipNewline(state);
674 node = new OperatorNode('or', 'or', [node, parseLogicalXor(state)]);
675 }
676
677 return node;
678 }
679
680 /**
681 * logical exclusive or, 'x xor y'
682 * @return {Node} node
683 * @private
684 */
685 function parseLogicalXor(state) {
686 var node = parseLogicalAnd(state);
687
688 while (state.token === 'xor') {
689 // eslint-disable-line no-unmodified-loop-condition
690 getTokenSkipNewline(state);
691 node = new OperatorNode('xor', 'xor', [node, parseLogicalAnd(state)]);
692 }
693
694 return node;
695 }
696
697 /**
698 * logical and, 'x and y'
699 * @return {Node} node
700 * @private
701 */
702 function parseLogicalAnd(state) {
703 var node = parseBitwiseOr(state);
704
705 while (state.token === 'and') {
706 // eslint-disable-line no-unmodified-loop-condition
707 getTokenSkipNewline(state);
708 node = new OperatorNode('and', 'and', [node, parseBitwiseOr(state)]);
709 }
710
711 return node;
712 }
713
714 /**
715 * bitwise or, 'x | y'
716 * @return {Node} node
717 * @private
718 */
719 function parseBitwiseOr(state) {
720 var node = parseBitwiseXor(state);
721
722 while (state.token === '|') {
723 // eslint-disable-line no-unmodified-loop-condition
724 getTokenSkipNewline(state);
725 node = new OperatorNode('|', 'bitOr', [node, parseBitwiseXor(state)]);
726 }
727
728 return node;
729 }
730
731 /**
732 * bitwise exclusive or (xor), 'x ^| y'
733 * @return {Node} node
734 * @private
735 */
736 function parseBitwiseXor(state) {
737 var node = parseBitwiseAnd(state);
738
739 while (state.token === '^|') {
740 // eslint-disable-line no-unmodified-loop-condition
741 getTokenSkipNewline(state);
742 node = new OperatorNode('^|', 'bitXor', [node, parseBitwiseAnd(state)]);
743 }
744
745 return node;
746 }
747
748 /**
749 * bitwise and, 'x & y'
750 * @return {Node} node
751 * @private
752 */
753 function parseBitwiseAnd(state) {
754 var node = parseRelational(state);
755
756 while (state.token === '&') {
757 // eslint-disable-line no-unmodified-loop-condition
758 getTokenSkipNewline(state);
759 node = new OperatorNode('&', 'bitAnd', [node, parseRelational(state)]);
760 }
761
762 return node;
763 }
764
765 /**
766 * relational operators
767 * @return {Node} node
768 * @private
769 */
770 function parseRelational(state) {
771 var node = void 0,
772 operators = void 0,
773 name = void 0,
774 fn = void 0,
775 params = void 0;
776
777 node = parseShift(state);
778
779 operators = {
780 '==': 'equal',
781 '!=': 'unequal',
782 '<': 'smaller',
783 '>': 'larger',
784 '<=': 'smallerEq',
785 '>=': 'largerEq'
786 };
787 while (operators.hasOwnProperty(state.token)) {
788 name = state.token;
789 fn = operators[name];
790
791 getTokenSkipNewline(state);
792 params = [node, parseShift(state)];
793 node = new OperatorNode(name, fn, params);
794 }
795
796 return node;
797 }
798
799 /**
800 * Bitwise left shift, bitwise right arithmetic shift, bitwise right logical shift
801 * @return {Node} node
802 * @private
803 */
804 function parseShift(state) {
805 var node = void 0,
806 operators = void 0,
807 name = void 0,
808 fn = void 0,
809 params = void 0;
810
811 node = parseConversion(state);
812
813 operators = {
814 '<<': 'leftShift',
815 '>>': 'rightArithShift',
816 '>>>': 'rightLogShift'
817 };
818
819 while (operators.hasOwnProperty(state.token)) {
820 name = state.token;
821 fn = operators[name];
822
823 getTokenSkipNewline(state);
824 params = [node, parseConversion(state)];
825 node = new OperatorNode(name, fn, params);
826 }
827
828 return node;
829 }
830
831 /**
832 * conversion operators 'to' and 'in'
833 * @return {Node} node
834 * @private
835 */
836 function parseConversion(state) {
837 var node = void 0,
838 operators = void 0,
839 name = void 0,
840 fn = void 0,
841 params = void 0;
842
843 node = parseRange(state);
844
845 operators = {
846 'to': 'to',
847 'in': 'to' // alias of 'to'
848 };
849
850 while (operators.hasOwnProperty(state.token)) {
851 name = state.token;
852 fn = operators[name];
853
854 getTokenSkipNewline(state);
855
856 if (name === 'in' && state.token === '') {
857 // end of expression -> this is the unit 'in' ('inch')
858 node = new OperatorNode('*', 'multiply', [node, new SymbolNode('in')], true);
859 } else {
860 // operator 'a to b' or 'a in b'
861 params = [node, parseRange(state)];
862 node = new OperatorNode(name, fn, params);
863 }
864 }
865
866 return node;
867 }
868
869 /**
870 * parse range, "start:end", "start:step:end", ":", "start:", ":end", etc
871 * @return {Node} node
872 * @private
873 */
874 function parseRange(state) {
875 var node = void 0;
876 var params = [];
877
878 if (state.token === ':') {
879 // implicit start=1 (one-based)
880 node = new ConstantNode(1);
881 } else {
882 // explicit start
883 node = parseAddSubtract(state);
884 }
885
886 if (state.token === ':' && state.conditionalLevel !== state.nestingLevel) {
887 // we ignore the range operator when a conditional operator is being processed on the same level
888 params.push(node);
889
890 // parse step and end
891 while (state.token === ':' && params.length < 3) {
892 // eslint-disable-line no-unmodified-loop-condition
893 getTokenSkipNewline(state);
894
895 if (state.token === ')' || state.token === ']' || state.token === ',' || state.token === '') {
896 // implicit end
897 params.push(new SymbolNode('end'));
898 } else {
899 // explicit end
900 params.push(parseAddSubtract(state));
901 }
902 }
903
904 if (params.length === 3) {
905 // params = [start, step, end]
906 node = new RangeNode(params[0], params[2], params[1]); // start, end, step
907 } else {
908 // length === 2
909 // params = [start, end]
910 node = new RangeNode(params[0], params[1]); // start, end
911 }
912 }
913
914 return node;
915 }
916
917 /**
918 * add or subtract
919 * @return {Node} node
920 * @private
921 */
922 function parseAddSubtract(state) {
923 var node = void 0,
924 operators = void 0,
925 name = void 0,
926 fn = void 0,
927 params = void 0;
928
929 node = parseMultiplyDivide(state);
930
931 operators = {
932 '+': 'add',
933 '-': 'subtract'
934 };
935 while (operators.hasOwnProperty(state.token)) {
936 name = state.token;
937 fn = operators[name];
938
939 getTokenSkipNewline(state);
940 params = [node, parseMultiplyDivide(state)];
941 node = new OperatorNode(name, fn, params);
942 }
943
944 return node;
945 }
946
947 /**
948 * multiply, divide, modulus
949 * @return {Node} node
950 * @private
951 */
952 function parseMultiplyDivide(state) {
953 var node = void 0,
954 last = void 0,
955 operators = void 0,
956 name = void 0,
957 fn = void 0;
958
959 node = parseImplicitMultiplication(state);
960 last = node;
961
962 operators = {
963 '*': 'multiply',
964 '.*': 'dotMultiply',
965 '/': 'divide',
966 './': 'dotDivide',
967 '%': 'mod',
968 'mod': 'mod'
969 };
970
971 while (true) {
972 if (operators.hasOwnProperty(state.token)) {
973 // explicit operators
974 name = state.token;
975 fn = operators[name];
976
977 getTokenSkipNewline(state);
978
979 last = parseImplicitMultiplication(state);
980 node = new OperatorNode(name, fn, [node, last]);
981 } else {
982 break;
983 }
984 }
985
986 return node;
987 }
988
989 /**
990 * implicit multiplication
991 * @return {Node} node
992 * @private
993 */
994 function parseImplicitMultiplication(state) {
995 var node = void 0,
996 last = void 0;
997
998 node = parseRule2(state);
999 last = node;
1000
1001 while (true) {
1002 if (state.tokenType === TOKENTYPE.SYMBOL || state.token === 'in' && type.isConstantNode(node) || state.tokenType === TOKENTYPE.NUMBER && !type.isConstantNode(last) && (!type.isOperatorNode(last) || last.op === '!') || state.token === '(') {
1003 // parse implicit multiplication
1004 //
1005 // symbol: implicit multiplication like '2a', '(2+3)a', 'a b'
1006 // number: implicit multiplication like '(2+3)2'
1007 // parenthesis: implicit multiplication like '2(3+4)', '(3+4)(1+2)'
1008 last = parseRule2(state);
1009 node = new OperatorNode('*', 'multiply', [node, last], true /* implicit */);
1010 } else {
1011 break;
1012 }
1013 }
1014
1015 return node;
1016 }
1017
1018 /**
1019 * Infamous "rule 2" as described in https://github.com/josdejong/mathjs/issues/792#issuecomment-361065370
1020 * Explicit division gets higher precedence than implicit multiplication
1021 * when the division matches this pattern: [number] / [number] [symbol]
1022 * @return {Node} node
1023 * @private
1024 */
1025 function parseRule2(state) {
1026 var node = parseUnary(state);
1027 var last = node;
1028 var tokenStates = [];
1029
1030 while (true) {
1031 // Match the "number /" part of the pattern "number / number symbol"
1032 if (state.token === '/' && type.isConstantNode(last)) {
1033 // Look ahead to see if the next token is a number
1034 tokenStates.push(_extends({}, state));
1035 getTokenSkipNewline(state);
1036
1037 // Match the "number / number" part of the pattern
1038 if (state.tokenType === TOKENTYPE.NUMBER) {
1039 // Look ahead again
1040 tokenStates.push(_extends({}, state));
1041 getTokenSkipNewline(state);
1042
1043 // Match the "symbol" part of the pattern, or a left parenthesis
1044 if (state.tokenType === TOKENTYPE.SYMBOL || state.token === '(') {
1045 // We've matched the pattern "number / number symbol".
1046 // Rewind once and build the "number / number" node; the symbol will be consumed later
1047 _extends(state, tokenStates.pop());
1048 tokenStates.pop();
1049 last = parseUnary(state);
1050 node = new OperatorNode('/', 'divide', [node, last]);
1051 } else {
1052 // Not a match, so rewind
1053 tokenStates.pop();
1054 _extends(state, tokenStates.pop());
1055 break;
1056 }
1057 } else {
1058 // Not a match, so rewind
1059 _extends(state, tokenStates.pop());
1060 break;
1061 }
1062 } else {
1063 break;
1064 }
1065 }
1066
1067 return node;
1068 }
1069
1070 /**
1071 * Unary plus and minus, and logical and bitwise not
1072 * @return {Node} node
1073 * @private
1074 */
1075 function parseUnary(state) {
1076 var name = void 0,
1077 params = void 0,
1078 fn = void 0;
1079 var operators = {
1080 '-': 'unaryMinus',
1081 '+': 'unaryPlus',
1082 '~': 'bitNot',
1083 'not': 'not'
1084 };
1085
1086 if (operators.hasOwnProperty(state.token)) {
1087 fn = operators[state.token];
1088 name = state.token;
1089
1090 getTokenSkipNewline(state);
1091 params = [parseUnary(state)];
1092
1093 return new OperatorNode(name, fn, params);
1094 }
1095
1096 return parsePow(state);
1097 }
1098
1099 /**
1100 * power
1101 * Note: power operator is right associative
1102 * @return {Node} node
1103 * @private
1104 */
1105 function parsePow(state) {
1106 var node = void 0,
1107 name = void 0,
1108 fn = void 0,
1109 params = void 0;
1110
1111 node = parseLeftHandOperators(state);
1112
1113 if (state.token === '^' || state.token === '.^') {
1114 name = state.token;
1115 fn = name === '^' ? 'pow' : 'dotPow';
1116
1117 getTokenSkipNewline(state);
1118 params = [node, parseUnary(state)]; // Go back to unary, we can have '2^-3'
1119 node = new OperatorNode(name, fn, params);
1120 }
1121
1122 return node;
1123 }
1124
1125 /**
1126 * Left hand operators: factorial x!, ctranspose x'
1127 * @return {Node} node
1128 * @private
1129 */
1130 function parseLeftHandOperators(state) {
1131 var node = void 0,
1132 operators = void 0,
1133 name = void 0,
1134 fn = void 0,
1135 params = void 0;
1136
1137 node = parseCustomNodes(state);
1138
1139 operators = {
1140 '!': 'factorial',
1141 '\'': 'ctranspose'
1142 };
1143
1144 while (operators.hasOwnProperty(state.token)) {
1145 name = state.token;
1146 fn = operators[name];
1147
1148 getToken(state);
1149 params = [node];
1150
1151 node = new OperatorNode(name, fn, params);
1152 node = parseAccessors(state, node);
1153 }
1154
1155 return node;
1156 }
1157
1158 /**
1159 * Parse a custom node handler. A node handler can be used to process
1160 * nodes in a custom way, for example for handling a plot.
1161 *
1162 * A handler must be passed as second argument of the parse function.
1163 * - must extend math.expression.node.Node
1164 * - must contain a function _compile(defs: Object) : string
1165 * - must contain a function find(filter: Object) : Node[]
1166 * - must contain a function toString() : string
1167 * - the constructor is called with a single argument containing all parameters
1168 *
1169 * For example:
1170 *
1171 * nodes = {
1172 * 'plot': PlotHandler
1173 * }
1174 *
1175 * The constructor of the handler is called as:
1176 *
1177 * node = new PlotHandler(params)
1178 *
1179 * The handler will be invoked when evaluating an expression like:
1180 *
1181 * node = math.parse('plot(sin(x), x)', nodes)
1182 *
1183 * @return {Node} node
1184 * @private
1185 */
1186 function parseCustomNodes(state) {
1187 var params = [];
1188
1189 if (state.tokenType === TOKENTYPE.SYMBOL && state.extraNodes.hasOwnProperty(state.token)) {
1190 var CustomNode = state.extraNodes[state.token];
1191
1192 getToken(state);
1193
1194 // parse parameters
1195 if (state.token === '(') {
1196 params = [];
1197
1198 openParams(state);
1199 getToken(state);
1200
1201 if (state.token !== ')') {
1202 params.push(parseAssignment(state));
1203
1204 // parse a list with parameters
1205 while (state.token === ',') {
1206 // eslint-disable-line no-unmodified-loop-condition
1207 getToken(state);
1208 params.push(parseAssignment(state));
1209 }
1210 }
1211
1212 if (state.token !== ')') {
1213 throw createSyntaxError(state, 'Parenthesis ) expected');
1214 }
1215 closeParams(state);
1216 getToken(state);
1217 }
1218
1219 // create a new custom node
1220 // noinspection JSValidateTypes
1221 return new CustomNode(params);
1222 }
1223
1224 return parseSymbol(state);
1225 }
1226
1227 /**
1228 * parse symbols: functions, variables, constants, units
1229 * @return {Node} node
1230 * @private
1231 */
1232 function parseSymbol(state) {
1233 var node = void 0,
1234 name = void 0;
1235
1236 if (state.tokenType === TOKENTYPE.SYMBOL || state.tokenType === TOKENTYPE.DELIMITER && state.token in NAMED_DELIMITERS) {
1237 name = state.token;
1238
1239 getToken(state);
1240
1241 if (CONSTANTS.hasOwnProperty(name)) {
1242 // true, false, null, ...
1243 node = new ConstantNode(CONSTANTS[name]);
1244 } else if (NUMERIC_CONSTANTS.indexOf(name) !== -1) {
1245 // NaN, Infinity
1246 node = new ConstantNode(numeric(name));
1247 } else {
1248 node = new SymbolNode(name);
1249 }
1250
1251 // parse function parameters and matrix index
1252 node = parseAccessors(state, node);
1253 return node;
1254 }
1255
1256 return parseString(state);
1257 }
1258
1259 /**
1260 * parse accessors:
1261 * - function invocation in round brackets (...), for example sqrt(2)
1262 * - index enclosed in square brackets [...], for example A[2,3]
1263 * - dot notation for properties, like foo.bar
1264 * @param {Node} node Node on which to apply the parameters. If there
1265 * are no parameters in the expression, the node
1266 * itself is returned
1267 * @param {string[]} [types] Filter the types of notations
1268 * can be ['(', '[', '.']
1269 * @return {Node} node
1270 * @private
1271 */
1272 function parseAccessors(state, node, types) {
1273 var params = void 0;
1274
1275 while ((state.token === '(' || state.token === '[' || state.token === '.') && (!types || types.indexOf(state.token) !== -1)) {
1276 // eslint-disable-line no-unmodified-loop-condition
1277 params = [];
1278
1279 if (state.token === '(') {
1280 if (type.isSymbolNode(node) || type.isAccessorNode(node)) {
1281 // function invocation like fn(2, 3) or obj.fn(2, 3)
1282 openParams(state);
1283 getToken(state);
1284
1285 if (state.token !== ')') {
1286 params.push(parseAssignment(state));
1287
1288 // parse a list with parameters
1289 while (state.token === ',') {
1290 // eslint-disable-line no-unmodified-loop-condition
1291 getToken(state);
1292 params.push(parseAssignment(state));
1293 }
1294 }
1295
1296 if (state.token !== ')') {
1297 throw createSyntaxError(state, 'Parenthesis ) expected');
1298 }
1299 closeParams(state);
1300 getToken(state);
1301
1302 node = new FunctionNode(node, params);
1303 } else {
1304 // implicit multiplication like (2+3)(4+5) or sqrt(2)(1+2)
1305 // don't parse it here but let it be handled by parseImplicitMultiplication
1306 // with correct precedence
1307 return node;
1308 }
1309 } else if (state.token === '[') {
1310 // index notation like variable[2, 3]
1311 openParams(state);
1312 getToken(state);
1313
1314 if (state.token !== ']') {
1315 params.push(parseAssignment(state));
1316
1317 // parse a list with parameters
1318 while (state.token === ',') {
1319 // eslint-disable-line no-unmodified-loop-condition
1320 getToken(state);
1321 params.push(parseAssignment(state));
1322 }
1323 }
1324
1325 if (state.token !== ']') {
1326 throw createSyntaxError(state, 'Parenthesis ] expected');
1327 }
1328 closeParams(state);
1329 getToken(state);
1330
1331 node = new AccessorNode(node, new IndexNode(params));
1332 } else {
1333 // dot notation like variable.prop
1334 getToken(state);
1335
1336 if (state.tokenType !== TOKENTYPE.SYMBOL) {
1337 throw createSyntaxError(state, 'Property name expected after dot');
1338 }
1339 params.push(new ConstantNode(state.token));
1340 getToken(state);
1341
1342 var dotNotation = true;
1343 node = new AccessorNode(node, new IndexNode(params, dotNotation));
1344 }
1345 }
1346
1347 return node;
1348 }
1349
1350 /**
1351 * parse a string.
1352 * A string is enclosed by double quotes
1353 * @return {Node} node
1354 * @private
1355 */
1356 function parseString(state) {
1357 var node = void 0,
1358 str = void 0;
1359
1360 if (state.token === '"') {
1361 str = parseStringToken(state);
1362
1363 // create constant
1364 node = new ConstantNode(str);
1365
1366 // parse index parameters
1367 node = parseAccessors(state, node);
1368
1369 return node;
1370 }
1371
1372 return parseMatrix(state);
1373 }
1374
1375 /**
1376 * Parse a string surrounded by double quotes "..."
1377 * @return {string}
1378 */
1379 function parseStringToken(state) {
1380 var str = '';
1381
1382 while (currentCharacter(state) !== '' && currentCharacter(state) !== '"') {
1383 if (currentCharacter(state) === '\\') {
1384 // escape character, immediately process the next
1385 // character to prevent stopping at a next '\"'
1386 str += currentCharacter(state);
1387 next(state);
1388 }
1389
1390 str += currentCharacter(state);
1391 next(state);
1392 }
1393
1394 getToken(state);
1395 if (state.token !== '"') {
1396 throw createSyntaxError(state, 'End of string " expected');
1397 }
1398 getToken(state);
1399
1400 return JSON.parse('"' + str + '"'); // unescape escaped characters
1401 }
1402
1403 /**
1404 * parse the matrix
1405 * @return {Node} node
1406 * @private
1407 */
1408 function parseMatrix(state) {
1409 var array = void 0,
1410 params = void 0,
1411 rows = void 0,
1412 cols = void 0;
1413
1414 if (state.token === '[') {
1415 // matrix [...]
1416 openParams(state);
1417 getToken(state);
1418
1419 if (state.token !== ']') {
1420 // this is a non-empty matrix
1421 var row = parseRow(state);
1422
1423 if (state.token === ';') {
1424 // 2 dimensional array
1425 rows = 1;
1426 params = [row];
1427
1428 // the rows of the matrix are separated by dot-comma's
1429 while (state.token === ';') {
1430 // eslint-disable-line no-unmodified-loop-condition
1431 getToken(state);
1432
1433 params[rows] = parseRow(state);
1434 rows++;
1435 }
1436
1437 if (state.token !== ']') {
1438 throw createSyntaxError(state, 'End of matrix ] expected');
1439 }
1440 closeParams(state);
1441 getToken(state);
1442
1443 // check if the number of columns matches in all rows
1444 cols = params[0].items.length;
1445 for (var r = 1; r < rows; r++) {
1446 if (params[r].items.length !== cols) {
1447 throw createError(state, 'Column dimensions mismatch ' + '(' + params[r].items.length + ' !== ' + cols + ')');
1448 }
1449 }
1450
1451 array = new ArrayNode(params);
1452 } else {
1453 // 1 dimensional vector
1454 if (state.token !== ']') {
1455 throw createSyntaxError(state, 'End of matrix ] expected');
1456 }
1457 closeParams(state);
1458 getToken(state);
1459
1460 array = row;
1461 }
1462 } else {
1463 // this is an empty matrix "[ ]"
1464 closeParams(state);
1465 getToken(state);
1466 array = new ArrayNode([]);
1467 }
1468
1469 return parseAccessors(state, array);
1470 }
1471
1472 return parseObject(state);
1473 }
1474
1475 /**
1476 * Parse a single comma-separated row from a matrix, like 'a, b, c'
1477 * @return {ArrayNode} node
1478 */
1479 function parseRow(state) {
1480 var params = [parseAssignment(state)];
1481 var len = 1;
1482
1483 while (state.token === ',') {
1484 // eslint-disable-line no-unmodified-loop-condition
1485 getToken(state);
1486
1487 // parse expression
1488 params[len] = parseAssignment(state);
1489 len++;
1490 }
1491
1492 return new ArrayNode(params);
1493 }
1494
1495 /**
1496 * parse an object, enclosed in angle brackets{...}, for example {value: 2}
1497 * @return {Node} node
1498 * @private
1499 */
1500 function parseObject(state) {
1501 if (state.token === '{') {
1502 var key = void 0;
1503
1504 var properties = {};
1505 do {
1506 getToken(state);
1507
1508 if (state.token !== '}') {
1509 // parse key
1510 if (state.token === '"') {
1511 key = parseStringToken(state);
1512 } else if (state.tokenType === TOKENTYPE.SYMBOL) {
1513 key = state.token;
1514 getToken(state);
1515 } else {
1516 throw createSyntaxError(state, 'Symbol or string expected as object key');
1517 }
1518
1519 // parse key/value separator
1520 if (state.token !== ':') {
1521 throw createSyntaxError(state, 'Colon : expected after object key');
1522 }
1523 getToken(state);
1524
1525 // parse key
1526 properties[key] = parseAssignment(state);
1527 }
1528 } while (state.token === ','); // eslint-disable-line no-unmodified-loop-condition
1529
1530 if (state.token !== '}') {
1531 throw createSyntaxError(state, 'Comma , or bracket } expected after object value');
1532 }
1533 getToken(state);
1534
1535 var node = new ObjectNode(properties);
1536
1537 // parse index parameters
1538 node = parseAccessors(state, node);
1539
1540 return node;
1541 }
1542
1543 return parseNumber(state);
1544 }
1545
1546 /**
1547 * parse a number
1548 * @return {Node} node
1549 * @private
1550 */
1551 function parseNumber(state) {
1552 var numberStr = void 0;
1553
1554 if (state.tokenType === TOKENTYPE.NUMBER) {
1555 // this is a number
1556 numberStr = state.token;
1557 getToken(state);
1558
1559 return new ConstantNode(numeric(numberStr, config.number));
1560 }
1561
1562 return parseParentheses(state);
1563 }
1564
1565 /**
1566 * parentheses
1567 * @return {Node} node
1568 * @private
1569 */
1570 function parseParentheses(state) {
1571 var node = void 0;
1572
1573 // check if it is a parenthesized expression
1574 if (state.token === '(') {
1575 // parentheses (...)
1576 openParams(state);
1577 getToken(state);
1578
1579 node = parseAssignment(state); // start again
1580
1581 if (state.token !== ')') {
1582 throw createSyntaxError(state, 'Parenthesis ) expected');
1583 }
1584 closeParams(state);
1585 getToken(state);
1586
1587 node = new ParenthesisNode(node);
1588 node = parseAccessors(state, node);
1589 return node;
1590 }
1591
1592 return parseEnd(state);
1593 }
1594
1595 /**
1596 * Evaluated when the expression is not yet ended but expected to end
1597 * @return {Node} res
1598 * @private
1599 */
1600 function parseEnd(state) {
1601 if (state.token === '') {
1602 // syntax error or unexpected end of expression
1603 throw createSyntaxError(state, 'Unexpected end of expression');
1604 } else if (state.token === "'") {
1605 throw createSyntaxError(state, 'Value expected. Note: strings must be enclosed by double quotes');
1606 } else {
1607 throw createSyntaxError(state, 'Value expected');
1608 }
1609 }
1610
1611 /**
1612 * Shortcut for getting the current row value (one based)
1613 * Returns the line of the currently handled expression
1614 * @private
1615 */
1616 /* TODO: implement keeping track on the row number
1617 function row () {
1618 return null
1619 }
1620 */
1621
1622 /**
1623 * Shortcut for getting the current col value (one based)
1624 * Returns the column (position) where the last state.token starts
1625 * @private
1626 */
1627 function col(state) {
1628 return state.index - state.token.length + 1;
1629 }
1630
1631 /**
1632 * Create an error
1633 * @param {string} message
1634 * @return {SyntaxError} instantiated error
1635 * @private
1636 */
1637 function createSyntaxError(state, message) {
1638 var c = col(state);
1639 var error = new SyntaxError(message + ' (char ' + c + ')');
1640 error['char'] = c;
1641
1642 return error;
1643 }
1644
1645 /**
1646 * Create an error
1647 * @param {string} message
1648 * @return {Error} instantiated error
1649 * @private
1650 */
1651 function createError(state, message) {
1652 var c = col(state);
1653 var error = new SyntaxError(message + ' (char ' + c + ')');
1654 error['char'] = c;
1655
1656 return error;
1657 }
1658
1659 return parse;
1660}
1661
1662exports.name = 'parse';
1663exports.path = 'expression';
1664exports.factory = factory;
\No newline at end of file