UNPKG

23.9 kBJavaScriptView Raw
1"use strict";
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6exports.createOperatorNode = void 0;
7
8var _is = require("../../utils/is");
9
10var _array = require("../../utils/array");
11
12var _string = require("../../utils/string");
13
14var _customs = require("../../utils/customs");
15
16var _operators = require("../operators");
17
18var _latex = require("../../utils/latex");
19
20var _factory = require("../../utils/factory");
21
22var name = 'OperatorNode';
23var dependencies = ['Node'];
24var createOperatorNode =
25/* #__PURE__ */
26(0, _factory.factory)(name, dependencies, function (_ref) {
27 var Node = _ref.Node;
28
29 /**
30 * @constructor OperatorNode
31 * @extends {Node}
32 * An operator with two arguments, like 2+3
33 *
34 * @param {string} op Operator name, for example '+'
35 * @param {string} fn Function name, for example 'add'
36 * @param {Node[]} args Operator arguments
37 * @param {boolean} [implicit] Is this an implicit multiplication?
38 */
39 function OperatorNode(op, fn, args, implicit) {
40 if (!(this instanceof OperatorNode)) {
41 throw new SyntaxError('Constructor must be called with the new operator');
42 } // validate input
43
44
45 if (typeof op !== 'string') {
46 throw new TypeError('string expected for parameter "op"');
47 }
48
49 if (typeof fn !== 'string') {
50 throw new TypeError('string expected for parameter "fn"');
51 }
52
53 if (!Array.isArray(args) || !args.every(_is.isNode)) {
54 throw new TypeError('Array containing Nodes expected for parameter "args"');
55 }
56
57 this.implicit = implicit === true;
58 this.op = op;
59 this.fn = fn;
60 this.args = args || [];
61 }
62
63 OperatorNode.prototype = new Node();
64 OperatorNode.prototype.type = 'OperatorNode';
65 OperatorNode.prototype.isOperatorNode = true;
66 /**
67 * Compile a node into a JavaScript function.
68 * This basically pre-calculates as much as possible and only leaves open
69 * calculations which depend on a dynamic scope with variables.
70 * @param {Object} math Math.js namespace with functions and constants.
71 * @param {Object} argNames An object with argument names as key and `true`
72 * as value. Used in the SymbolNode to optimize
73 * for arguments from user assigned functions
74 * (see FunctionAssignmentNode) or special symbols
75 * like `end` (see IndexNode).
76 * @return {function} Returns a function which can be called like:
77 * evalNode(scope: Object, args: Object, context: *)
78 */
79
80 OperatorNode.prototype._compile = function (math, argNames) {
81 // validate fn
82 if (typeof this.fn !== 'string' || !(0, _customs.isSafeMethod)(math, this.fn)) {
83 if (!math[this.fn]) {
84 throw new Error('Function ' + this.fn + ' missing in provided namespace "math"');
85 } else {
86 throw new Error('No access to function "' + this.fn + '"');
87 }
88 }
89
90 var fn = (0, _customs.getSafeProperty)(math, this.fn);
91 var evalArgs = (0, _array.map)(this.args, function (arg) {
92 return arg._compile(math, argNames);
93 });
94
95 if (evalArgs.length === 1) {
96 var evalArg0 = evalArgs[0];
97 return function evalOperatorNode(scope, args, context) {
98 return fn(evalArg0(scope, args, context));
99 };
100 } else if (evalArgs.length === 2) {
101 var _evalArg = evalArgs[0];
102 var evalArg1 = evalArgs[1];
103 return function evalOperatorNode(scope, args, context) {
104 return fn(_evalArg(scope, args, context), evalArg1(scope, args, context));
105 };
106 } else {
107 return function evalOperatorNode(scope, args, context) {
108 return fn.apply(null, (0, _array.map)(evalArgs, function (evalArg) {
109 return evalArg(scope, args, context);
110 }));
111 };
112 }
113 };
114 /**
115 * Execute a callback for each of the child nodes of this node
116 * @param {function(child: Node, path: string, parent: Node)} callback
117 */
118
119
120 OperatorNode.prototype.forEach = function (callback) {
121 for (var i = 0; i < this.args.length; i++) {
122 callback(this.args[i], 'args[' + i + ']', this);
123 }
124 };
125 /**
126 * Create a new OperatorNode having it's childs be the results of calling
127 * the provided callback function for each of the childs of the original node.
128 * @param {function(child: Node, path: string, parent: Node): Node} callback
129 * @returns {OperatorNode} Returns a transformed copy of the node
130 */
131
132
133 OperatorNode.prototype.map = function (callback) {
134 var args = [];
135
136 for (var i = 0; i < this.args.length; i++) {
137 args[i] = this._ifNode(callback(this.args[i], 'args[' + i + ']', this));
138 }
139
140 return new OperatorNode(this.op, this.fn, args, this.implicit);
141 };
142 /**
143 * Create a clone of this node, a shallow copy
144 * @return {OperatorNode}
145 */
146
147
148 OperatorNode.prototype.clone = function () {
149 return new OperatorNode(this.op, this.fn, this.args.slice(0), this.implicit);
150 };
151 /**
152 * Check whether this is an unary OperatorNode:
153 * has exactly one argument, like `-a`.
154 * @return {boolean} Returns true when an unary operator node, false otherwise.
155 */
156
157
158 OperatorNode.prototype.isUnary = function () {
159 return this.args.length === 1;
160 };
161 /**
162 * Check whether this is a binary OperatorNode:
163 * has exactly two arguments, like `a + b`.
164 * @return {boolean} Returns true when a binary operator node, false otherwise.
165 */
166
167
168 OperatorNode.prototype.isBinary = function () {
169 return this.args.length === 2;
170 };
171 /**
172 * Calculate which parentheses are necessary. Gets an OperatorNode
173 * (which is the root of the tree) and an Array of Nodes
174 * (this.args) and returns an array where 'true' means that an argument
175 * has to be enclosed in parentheses whereas 'false' means the opposite.
176 *
177 * @param {OperatorNode} root
178 * @param {string} parenthesis
179 * @param {Node[]} args
180 * @param {boolean} latex
181 * @return {boolean[]}
182 * @private
183 */
184
185
186 function calculateNecessaryParentheses(root, parenthesis, implicit, args, latex) {
187 // precedence of the root OperatorNode
188 var precedence = (0, _operators.getPrecedence)(root, parenthesis);
189 var associativity = (0, _operators.getAssociativity)(root, parenthesis);
190
191 if (parenthesis === 'all' || args.length > 2 && root.getIdentifier() !== 'OperatorNode:add' && root.getIdentifier() !== 'OperatorNode:multiply') {
192 return args.map(function (arg) {
193 switch (arg.getContent().type) {
194 // Nodes that don't need extra parentheses
195 case 'ArrayNode':
196 case 'ConstantNode':
197 case 'SymbolNode':
198 case 'ParenthesisNode':
199 return false;
200
201 default:
202 return true;
203 }
204 });
205 }
206
207 var result;
208
209 switch (args.length) {
210 case 0:
211 result = [];
212 break;
213
214 case 1:
215 // unary operators
216 {
217 // precedence of the operand
218 var operandPrecedence = (0, _operators.getPrecedence)(args[0], parenthesis); // handle special cases for LaTeX, where some of the parentheses aren't needed
219
220 if (latex && operandPrecedence !== null) {
221 var operandIdentifier;
222 var rootIdentifier;
223
224 if (parenthesis === 'keep') {
225 operandIdentifier = args[0].getIdentifier();
226 rootIdentifier = root.getIdentifier();
227 } else {
228 // Ignore Parenthesis Nodes when not in 'keep' mode
229 operandIdentifier = args[0].getContent().getIdentifier();
230 rootIdentifier = root.getContent().getIdentifier();
231 }
232
233 if (_operators.properties[precedence][rootIdentifier].latexLeftParens === false) {
234 result = [false];
235 break;
236 }
237
238 if (_operators.properties[operandPrecedence][operandIdentifier].latexParens === false) {
239 result = [false];
240 break;
241 }
242 }
243
244 if (operandPrecedence === null) {
245 // if the operand has no defined precedence, no parens are needed
246 result = [false];
247 break;
248 }
249
250 if (operandPrecedence <= precedence) {
251 // if the operands precedence is lower, parens are needed
252 result = [true];
253 break;
254 } // otherwise, no parens needed
255
256
257 result = [false];
258 }
259 break;
260
261 case 2:
262 // binary operators
263 {
264 var lhsParens; // left hand side needs parenthesis?
265 // precedence of the left hand side
266
267 var lhsPrecedence = (0, _operators.getPrecedence)(args[0], parenthesis); // is the root node associative with the left hand side
268
269 var assocWithLhs = (0, _operators.isAssociativeWith)(root, args[0], parenthesis);
270
271 if (lhsPrecedence === null) {
272 // if the left hand side has no defined precedence, no parens are needed
273 // FunctionNode for example
274 lhsParens = false;
275 } else if (lhsPrecedence === precedence && associativity === 'right' && !assocWithLhs) {
276 // In case of equal precedence, if the root node is left associative
277 // parens are **never** necessary for the left hand side.
278 // If it is right associative however, parens are necessary
279 // if the root node isn't associative with the left hand side
280 lhsParens = true;
281 } else if (lhsPrecedence < precedence) {
282 lhsParens = true;
283 } else {
284 lhsParens = false;
285 }
286
287 var rhsParens; // right hand side needs parenthesis?
288 // precedence of the right hand side
289
290 var rhsPrecedence = (0, _operators.getPrecedence)(args[1], parenthesis); // is the root node associative with the right hand side?
291
292 var assocWithRhs = (0, _operators.isAssociativeWith)(root, args[1], parenthesis);
293
294 if (rhsPrecedence === null) {
295 // if the right hand side has no defined precedence, no parens are needed
296 // FunctionNode for example
297 rhsParens = false;
298 } else if (rhsPrecedence === precedence && associativity === 'left' && !assocWithRhs) {
299 // In case of equal precedence, if the root node is right associative
300 // parens are **never** necessary for the right hand side.
301 // If it is left associative however, parens are necessary
302 // if the root node isn't associative with the right hand side
303 rhsParens = true;
304 } else if (rhsPrecedence < precedence) {
305 rhsParens = true;
306 } else {
307 rhsParens = false;
308 } // handle special cases for LaTeX, where some of the parentheses aren't needed
309
310
311 if (latex) {
312 var _rootIdentifier;
313
314 var lhsIdentifier;
315 var rhsIdentifier;
316
317 if (parenthesis === 'keep') {
318 _rootIdentifier = root.getIdentifier();
319 lhsIdentifier = root.args[0].getIdentifier();
320 rhsIdentifier = root.args[1].getIdentifier();
321 } else {
322 // Ignore ParenthesisNodes when not in 'keep' mode
323 _rootIdentifier = root.getContent().getIdentifier();
324 lhsIdentifier = root.args[0].getContent().getIdentifier();
325 rhsIdentifier = root.args[1].getContent().getIdentifier();
326 }
327
328 if (lhsPrecedence !== null) {
329 if (_operators.properties[precedence][_rootIdentifier].latexLeftParens === false) {
330 lhsParens = false;
331 }
332
333 if (_operators.properties[lhsPrecedence][lhsIdentifier].latexParens === false) {
334 lhsParens = false;
335 }
336 }
337
338 if (rhsPrecedence !== null) {
339 if (_operators.properties[precedence][_rootIdentifier].latexRightParens === false) {
340 rhsParens = false;
341 }
342
343 if (_operators.properties[rhsPrecedence][rhsIdentifier].latexParens === false) {
344 rhsParens = false;
345 }
346 }
347 }
348
349 result = [lhsParens, rhsParens];
350 }
351 break;
352
353 default:
354 if (root.getIdentifier() === 'OperatorNode:add' || root.getIdentifier() === 'OperatorNode:multiply') {
355 result = args.map(function (arg) {
356 var argPrecedence = (0, _operators.getPrecedence)(arg, parenthesis);
357 var assocWithArg = (0, _operators.isAssociativeWith)(root, arg, parenthesis);
358 var argAssociativity = (0, _operators.getAssociativity)(arg, parenthesis);
359
360 if (argPrecedence === null) {
361 // if the argument has no defined precedence, no parens are needed
362 return false;
363 } else if (precedence === argPrecedence && associativity === argAssociativity && !assocWithArg) {
364 return true;
365 } else if (argPrecedence < precedence) {
366 return true;
367 }
368
369 return false;
370 });
371 }
372
373 break;
374 } // handles an edge case of 'auto' parentheses with implicit multiplication of ConstantNode
375 // In that case print parentheses for ParenthesisNodes even though they normally wouldn't be
376 // printed.
377
378
379 if (args.length >= 2 && root.getIdentifier() === 'OperatorNode:multiply' && root.implicit && parenthesis === 'auto' && implicit === 'hide') {
380 result = args.map(function (arg, index) {
381 var isParenthesisNode = arg.getIdentifier() === 'ParenthesisNode';
382
383 if (result[index] || isParenthesisNode) {
384 // put in parenthesis?
385 return true;
386 }
387
388 return false;
389 });
390 }
391
392 return result;
393 }
394 /**
395 * Get string representation.
396 * @param {Object} options
397 * @return {string} str
398 */
399
400
401 OperatorNode.prototype._toString = function (options) {
402 var parenthesis = options && options.parenthesis ? options.parenthesis : 'keep';
403 var implicit = options && options.implicit ? options.implicit : 'hide';
404 var args = this.args;
405 var parens = calculateNecessaryParentheses(this, parenthesis, implicit, args, false);
406
407 if (args.length === 1) {
408 // unary operators
409 var assoc = (0, _operators.getAssociativity)(this, parenthesis);
410 var operand = args[0].toString(options);
411
412 if (parens[0]) {
413 operand = '(' + operand + ')';
414 } // for example for "not", we want a space between operand and argument
415
416
417 var opIsNamed = /[a-zA-Z]+/.test(this.op);
418
419 if (assoc === 'right') {
420 // prefix operator
421 return this.op + (opIsNamed ? ' ' : '') + operand;
422 } else if (assoc === 'left') {
423 // postfix
424 return operand + (opIsNamed ? ' ' : '') + this.op;
425 } // fall back to postfix
426
427
428 return operand + this.op;
429 } else if (args.length === 2) {
430 var lhs = args[0].toString(options); // left hand side
431
432 var rhs = args[1].toString(options); // right hand side
433
434 if (parens[0]) {
435 // left hand side in parenthesis?
436 lhs = '(' + lhs + ')';
437 }
438
439 if (parens[1]) {
440 // right hand side in parenthesis?
441 rhs = '(' + rhs + ')';
442 }
443
444 if (this.implicit && this.getIdentifier() === 'OperatorNode:multiply' && implicit === 'hide') {
445 return lhs + ' ' + rhs;
446 }
447
448 return lhs + ' ' + this.op + ' ' + rhs;
449 } else if (args.length > 2 && (this.getIdentifier() === 'OperatorNode:add' || this.getIdentifier() === 'OperatorNode:multiply')) {
450 var stringifiedArgs = args.map(function (arg, index) {
451 arg = arg.toString(options);
452
453 if (parens[index]) {
454 // put in parenthesis?
455 arg = '(' + arg + ')';
456 }
457
458 return arg;
459 });
460
461 if (this.implicit && this.getIdentifier() === 'OperatorNode:multiply' && implicit === 'hide') {
462 return stringifiedArgs.join(' ');
463 }
464
465 return stringifiedArgs.join(' ' + this.op + ' ');
466 } else {
467 // fallback to formatting as a function call
468 return this.fn + '(' + this.args.join(', ') + ')';
469 }
470 };
471 /**
472 * Get a JSON representation of the node
473 * @returns {Object}
474 */
475
476
477 OperatorNode.prototype.toJSON = function () {
478 return {
479 mathjs: 'OperatorNode',
480 op: this.op,
481 fn: this.fn,
482 args: this.args,
483 implicit: this.implicit
484 };
485 };
486 /**
487 * Instantiate an OperatorNode from its JSON representation
488 * @param {Object} json An object structured like
489 * `{"mathjs": "OperatorNode", "op": "+", "fn": "add", "args": [...], "implicit": false}`,
490 * where mathjs is optional
491 * @returns {OperatorNode}
492 */
493
494
495 OperatorNode.fromJSON = function (json) {
496 return new OperatorNode(json.op, json.fn, json.args, json.implicit);
497 };
498 /**
499 * Get HTML representation.
500 * @param {Object} options
501 * @return {string} str
502 */
503
504
505 OperatorNode.prototype.toHTML = function (options) {
506 var parenthesis = options && options.parenthesis ? options.parenthesis : 'keep';
507 var implicit = options && options.implicit ? options.implicit : 'hide';
508 var args = this.args;
509 var parens = calculateNecessaryParentheses(this, parenthesis, implicit, args, false);
510
511 if (args.length === 1) {
512 // unary operators
513 var assoc = (0, _operators.getAssociativity)(this, parenthesis);
514 var operand = args[0].toHTML(options);
515
516 if (parens[0]) {
517 operand = '<span class="math-parenthesis math-round-parenthesis">(</span>' + operand + '<span class="math-parenthesis math-round-parenthesis">)</span>';
518 }
519
520 if (assoc === 'right') {
521 // prefix operator
522 return '<span class="math-operator math-unary-operator math-lefthand-unary-operator">' + (0, _string.escape)(this.op) + '</span>' + operand;
523 } else {
524 // postfix when assoc === 'left' or undefined
525 return operand + '<span class="math-operator math-unary-operator math-righthand-unary-operator">' + (0, _string.escape)(this.op) + '</span>';
526 }
527 } else if (args.length === 2) {
528 // binary operatoes
529 var lhs = args[0].toHTML(options); // left hand side
530
531 var rhs = args[1].toHTML(options); // right hand side
532
533 if (parens[0]) {
534 // left hand side in parenthesis?
535 lhs = '<span class="math-parenthesis math-round-parenthesis">(</span>' + lhs + '<span class="math-parenthesis math-round-parenthesis">)</span>';
536 }
537
538 if (parens[1]) {
539 // right hand side in parenthesis?
540 rhs = '<span class="math-parenthesis math-round-parenthesis">(</span>' + rhs + '<span class="math-parenthesis math-round-parenthesis">)</span>';
541 }
542
543 if (this.implicit && this.getIdentifier() === 'OperatorNode:multiply' && implicit === 'hide') {
544 return lhs + '<span class="math-operator math-binary-operator math-implicit-binary-operator"></span>' + rhs;
545 }
546
547 return lhs + '<span class="math-operator math-binary-operator math-explicit-binary-operator">' + (0, _string.escape)(this.op) + '</span>' + rhs;
548 } else {
549 var stringifiedArgs = args.map(function (arg, index) {
550 arg = arg.toHTML(options);
551
552 if (parens[index]) {
553 // put in parenthesis?
554 arg = '<span class="math-parenthesis math-round-parenthesis">(</span>' + arg + '<span class="math-parenthesis math-round-parenthesis">)</span>';
555 }
556
557 return arg;
558 });
559
560 if (args.length > 2 && (this.getIdentifier() === 'OperatorNode:add' || this.getIdentifier() === 'OperatorNode:multiply')) {
561 if (this.implicit && this.getIdentifier() === 'OperatorNode:multiply' && implicit === 'hide') {
562 return stringifiedArgs.join('<span class="math-operator math-binary-operator math-implicit-binary-operator"></span>');
563 }
564
565 return stringifiedArgs.join('<span class="math-operator math-binary-operator math-explicit-binary-operator">' + (0, _string.escape)(this.op) + '</span>');
566 } else {
567 // fallback to formatting as a function call
568 return '<span class="math-function">' + (0, _string.escape)(this.fn) + '</span><span class="math-paranthesis math-round-parenthesis">(</span>' + stringifiedArgs.join('<span class="math-separator">,</span>') + '<span class="math-paranthesis math-round-parenthesis">)</span>';
569 }
570 }
571 };
572 /**
573 * Get LaTeX representation
574 * @param {Object} options
575 * @return {string} str
576 */
577
578
579 OperatorNode.prototype._toTex = function (options) {
580 var parenthesis = options && options.parenthesis ? options.parenthesis : 'keep';
581 var implicit = options && options.implicit ? options.implicit : 'hide';
582 var args = this.args;
583 var parens = calculateNecessaryParentheses(this, parenthesis, implicit, args, true);
584 var op = _latex.latexOperators[this.fn];
585 op = typeof op === 'undefined' ? this.op : op; // fall back to using this.op
586
587 if (args.length === 1) {
588 // unary operators
589 var assoc = (0, _operators.getAssociativity)(this, parenthesis);
590 var operand = args[0].toTex(options);
591
592 if (parens[0]) {
593 operand = "\\left(".concat(operand, "\\right)");
594 }
595
596 if (assoc === 'right') {
597 // prefix operator
598 return op + operand;
599 } else if (assoc === 'left') {
600 // postfix operator
601 return operand + op;
602 } // fall back to postfix
603
604
605 return operand + op;
606 } else if (args.length === 2) {
607 // binary operators
608 var lhs = args[0]; // left hand side
609
610 var lhsTex = lhs.toTex(options);
611
612 if (parens[0]) {
613 lhsTex = "\\left(".concat(lhsTex, "\\right)");
614 }
615
616 var rhs = args[1]; // right hand side
617
618 var rhsTex = rhs.toTex(options);
619
620 if (parens[1]) {
621 rhsTex = "\\left(".concat(rhsTex, "\\right)");
622 } // handle some exceptions (due to the way LaTeX works)
623
624
625 var lhsIdentifier;
626
627 if (parenthesis === 'keep') {
628 lhsIdentifier = lhs.getIdentifier();
629 } else {
630 // Ignore ParenthesisNodes if in 'keep' mode
631 lhsIdentifier = lhs.getContent().getIdentifier();
632 }
633
634 switch (this.getIdentifier()) {
635 case 'OperatorNode:divide':
636 // op contains '\\frac' at this point
637 return op + '{' + lhsTex + '}' + '{' + rhsTex + '}';
638
639 case 'OperatorNode:pow':
640 lhsTex = '{' + lhsTex + '}';
641 rhsTex = '{' + rhsTex + '}';
642
643 switch (lhsIdentifier) {
644 case 'ConditionalNode': //
645
646 case 'OperatorNode:divide':
647 lhsTex = "\\left(".concat(lhsTex, "\\right)");
648 }
649
650 break;
651
652 case 'OperatorNode:multiply':
653 if (this.implicit && implicit === 'hide') {
654 return lhsTex + '~' + rhsTex;
655 }
656
657 }
658
659 return lhsTex + op + rhsTex;
660 } else if (args.length > 2 && (this.getIdentifier() === 'OperatorNode:add' || this.getIdentifier() === 'OperatorNode:multiply')) {
661 var texifiedArgs = args.map(function (arg, index) {
662 arg = arg.toTex(options);
663
664 if (parens[index]) {
665 arg = "\\left(".concat(arg, "\\right)");
666 }
667
668 return arg;
669 });
670
671 if (this.getIdentifier() === 'OperatorNode:multiply' && this.implicit) {
672 return texifiedArgs.join('~');
673 }
674
675 return texifiedArgs.join(op);
676 } else {
677 // fall back to formatting as a function call
678 // as this is a fallback, it doesn't use
679 // fancy function names
680 return '\\mathrm{' + this.fn + '}\\left(' + args.map(function (arg) {
681 return arg.toTex(options);
682 }).join(',') + '\\right)';
683 }
684 };
685 /**
686 * Get identifier.
687 * @return {string}
688 */
689
690
691 OperatorNode.prototype.getIdentifier = function () {
692 return this.type + ':' + this.fn;
693 };
694
695 return OperatorNode;
696}, {
697 isClass: true,
698 isNode: true
699});
700exports.createOperatorNode = createOperatorNode;
\No newline at end of file