UNPKG

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