simple-squiggle

A restricted subset of Squiggle
Log | Files | Refs | README

OperatorNode.js (23677B)


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