simple-squiggle

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

FunctionAssignmentNode.js (8290B)


      1 import { isNode } from '../../utils/is.js';
      2 import { keywords } from '../keywords.js';
      3 import { escape } from '../../utils/string.js';
      4 import { forEach, join } from '../../utils/array.js';
      5 import { toSymbol } from '../../utils/latex.js';
      6 import { getPrecedence } from '../operators.js';
      7 import { factory } from '../../utils/factory.js';
      8 var name = 'FunctionAssignmentNode';
      9 var dependencies = ['typed', 'Node'];
     10 export var createFunctionAssignmentNode = /* #__PURE__ */factory(name, dependencies, _ref => {
     11   var {
     12     typed,
     13     Node
     14   } = _ref;
     15 
     16   /**
     17    * @constructor FunctionAssignmentNode
     18    * @extends {Node}
     19    * Function assignment
     20    *
     21    * @param {string} name           Function name
     22    * @param {string[] | Array.<{name: string, type: string}>} params
     23    *                                Array with function parameter names, or an
     24    *                                array with objects containing the name
     25    *                                and type of the parameter
     26    * @param {Node} expr             The function expression
     27    */
     28   function FunctionAssignmentNode(name, params, expr) {
     29     if (!(this instanceof FunctionAssignmentNode)) {
     30       throw new SyntaxError('Constructor must be called with the new operator');
     31     } // validate input
     32 
     33 
     34     if (typeof name !== 'string') throw new TypeError('String expected for parameter "name"');
     35     if (!Array.isArray(params)) throw new TypeError('Array containing strings or objects expected for parameter "params"');
     36     if (!isNode(expr)) throw new TypeError('Node expected for parameter "expr"');
     37     if (keywords.has(name)) throw new Error('Illegal function name, "' + name + '" is a reserved keyword');
     38     this.name = name;
     39     this.params = params.map(function (param) {
     40       return param && param.name || param;
     41     });
     42     this.types = params.map(function (param) {
     43       return param && param.type || 'any';
     44     });
     45     this.expr = expr;
     46   }
     47 
     48   FunctionAssignmentNode.prototype = new Node();
     49   FunctionAssignmentNode.prototype.type = 'FunctionAssignmentNode';
     50   FunctionAssignmentNode.prototype.isFunctionAssignmentNode = true;
     51   /**
     52    * Compile a node into a JavaScript function.
     53    * This basically pre-calculates as much as possible and only leaves open
     54    * calculations which depend on a dynamic scope with variables.
     55    * @param {Object} math     Math.js namespace with functions and constants.
     56    * @param {Object} argNames An object with argument names as key and `true`
     57    *                          as value. Used in the SymbolNode to optimize
     58    *                          for arguments from user assigned functions
     59    *                          (see FunctionAssignmentNode) or special symbols
     60    *                          like `end` (see IndexNode).
     61    * @return {function} Returns a function which can be called like:
     62    *                        evalNode(scope: Object, args: Object, context: *)
     63    */
     64 
     65   FunctionAssignmentNode.prototype._compile = function (math, argNames) {
     66     var childArgNames = Object.create(argNames);
     67     forEach(this.params, function (param) {
     68       childArgNames[param] = true;
     69     }); // compile the function expression with the child args
     70 
     71     var evalExpr = this.expr._compile(math, childArgNames);
     72 
     73     var name = this.name;
     74     var params = this.params;
     75     var signature = join(this.types, ',');
     76     var syntax = name + '(' + join(this.params, ', ') + ')';
     77     return function evalFunctionAssignmentNode(scope, args, context) {
     78       var signatures = {};
     79 
     80       signatures[signature] = function () {
     81         var childArgs = Object.create(args);
     82 
     83         for (var i = 0; i < params.length; i++) {
     84           childArgs[params[i]] = arguments[i];
     85         }
     86 
     87         return evalExpr(scope, childArgs, context);
     88       };
     89 
     90       var fn = typed(name, signatures);
     91       fn.syntax = syntax;
     92       scope.set(name, fn);
     93       return fn;
     94     };
     95   };
     96   /**
     97    * Execute a callback for each of the child nodes of this node
     98    * @param {function(child: Node, path: string, parent: Node)} callback
     99    */
    100 
    101 
    102   FunctionAssignmentNode.prototype.forEach = function (callback) {
    103     callback(this.expr, 'expr', this);
    104   };
    105   /**
    106    * Create a new FunctionAssignmentNode having it's childs be the results of calling
    107    * the provided callback function for each of the childs of the original node.
    108    * @param {function(child: Node, path: string, parent: Node): Node} callback
    109    * @returns {FunctionAssignmentNode} Returns a transformed copy of the node
    110    */
    111 
    112 
    113   FunctionAssignmentNode.prototype.map = function (callback) {
    114     var expr = this._ifNode(callback(this.expr, 'expr', this));
    115 
    116     return new FunctionAssignmentNode(this.name, this.params.slice(0), expr);
    117   };
    118   /**
    119    * Create a clone of this node, a shallow copy
    120    * @return {FunctionAssignmentNode}
    121    */
    122 
    123 
    124   FunctionAssignmentNode.prototype.clone = function () {
    125     return new FunctionAssignmentNode(this.name, this.params.slice(0), this.expr);
    126   };
    127   /**
    128    * Is parenthesis needed?
    129    * @param {Node} node
    130    * @param {Object} parenthesis
    131    * @private
    132    */
    133 
    134 
    135   function needParenthesis(node, parenthesis) {
    136     var precedence = getPrecedence(node, parenthesis);
    137     var exprPrecedence = getPrecedence(node.expr, parenthesis);
    138     return parenthesis === 'all' || exprPrecedence !== null && exprPrecedence <= precedence;
    139   }
    140   /**
    141    * get string representation
    142    * @param {Object} options
    143    * @return {string} str
    144    */
    145 
    146 
    147   FunctionAssignmentNode.prototype._toString = function (options) {
    148     var parenthesis = options && options.parenthesis ? options.parenthesis : 'keep';
    149     var expr = this.expr.toString(options);
    150 
    151     if (needParenthesis(this, parenthesis)) {
    152       expr = '(' + expr + ')';
    153     }
    154 
    155     return this.name + '(' + this.params.join(', ') + ') = ' + expr;
    156   };
    157   /**
    158    * Get a JSON representation of the node
    159    * @returns {Object}
    160    */
    161 
    162 
    163   FunctionAssignmentNode.prototype.toJSON = function () {
    164     var types = this.types;
    165     return {
    166       mathjs: 'FunctionAssignmentNode',
    167       name: this.name,
    168       params: this.params.map(function (param, index) {
    169         return {
    170           name: param,
    171           type: types[index]
    172         };
    173       }),
    174       expr: this.expr
    175     };
    176   };
    177   /**
    178    * Instantiate an FunctionAssignmentNode from its JSON representation
    179    * @param {Object} json  An object structured like
    180    *                       `{"mathjs": "FunctionAssignmentNode", name: ..., params: ..., expr: ...}`,
    181    *                       where mathjs is optional
    182    * @returns {FunctionAssignmentNode}
    183    */
    184 
    185 
    186   FunctionAssignmentNode.fromJSON = function (json) {
    187     return new FunctionAssignmentNode(json.name, json.params, json.expr);
    188   };
    189   /**
    190    * get HTML representation
    191    * @param {Object} options
    192    * @return {string} str
    193    */
    194 
    195 
    196   FunctionAssignmentNode.prototype.toHTML = function (options) {
    197     var parenthesis = options && options.parenthesis ? options.parenthesis : 'keep';
    198     var params = [];
    199 
    200     for (var i = 0; i < this.params.length; i++) {
    201       params.push('<span class="math-symbol math-parameter">' + escape(this.params[i]) + '</span>');
    202     }
    203 
    204     var expr = this.expr.toHTML(options);
    205 
    206     if (needParenthesis(this, parenthesis)) {
    207       expr = '<span class="math-parenthesis math-round-parenthesis">(</span>' + expr + '<span class="math-parenthesis math-round-parenthesis">)</span>';
    208     }
    209 
    210     return '<span class="math-function">' + escape(this.name) + '</span>' + '<span class="math-parenthesis math-round-parenthesis">(</span>' + params.join('<span class="math-separator">,</span>') + '<span class="math-parenthesis math-round-parenthesis">)</span><span class="math-operator math-assignment-operator math-variable-assignment-operator math-binary-operator">=</span>' + expr;
    211   };
    212   /**
    213    * get LaTeX representation
    214    * @param {Object} options
    215    * @return {string} str
    216    */
    217 
    218 
    219   FunctionAssignmentNode.prototype._toTex = function (options) {
    220     var parenthesis = options && options.parenthesis ? options.parenthesis : 'keep';
    221     var expr = this.expr.toTex(options);
    222 
    223     if (needParenthesis(this, parenthesis)) {
    224       expr = "\\left(".concat(expr, "\\right)");
    225     }
    226 
    227     return '\\mathrm{' + this.name + '}\\left(' + this.params.map(toSymbol).join(',') + '\\right):=' + expr;
    228   };
    229 
    230   return FunctionAssignmentNode;
    231 }, {
    232   isClass: true,
    233   isNode: true
    234 });