simple-squiggle

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

import.js (13284B)


      1 "use strict";
      2 
      3 var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
      4 
      5 Object.defineProperty(exports, "__esModule", {
      6   value: true
      7 });
      8 exports.importFactory = importFactory;
      9 
     10 var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
     11 
     12 var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof"));
     13 
     14 var _is = require("../../utils/is.js");
     15 
     16 var _factory = require("../../utils/factory.js");
     17 
     18 var _object = require("../../utils/object.js");
     19 
     20 var _array = require("../../utils/array.js");
     21 
     22 var _ArgumentsError = require("../../error/ArgumentsError.js");
     23 
     24 function importFactory(typed, load, math, importedFactories) {
     25   /**
     26    * Import functions from an object or a module.
     27    *
     28    * This function is only available on a mathjs instance created using `create`.
     29    *
     30    * Syntax:
     31    *
     32    *    math.import(functions)
     33    *    math.import(functions, options)
     34    *
     35    * Where:
     36    *
     37    * - `functions: Object`
     38    *   An object with functions or factories to be imported.
     39    * - `options: Object` An object with import options. Available options:
     40    *   - `override: boolean`
     41    *     If true, existing functions will be overwritten. False by default.
     42    *   - `silent: boolean`
     43    *     If true, the function will not throw errors on duplicates or invalid
     44    *     types. False by default.
     45    *   - `wrap: boolean`
     46    *     If true, the functions will be wrapped in a wrapper function
     47    *     which converts data types like Matrix to primitive data types like Array.
     48    *     The wrapper is needed when extending math.js with libraries which do not
     49    *     support these data type. False by default.
     50    *
     51    * Examples:
     52    *
     53    *    import { create, all } from 'mathjs'
     54    *    import * as numbers from 'numbers'
     55    *
     56    *    // create a mathjs instance
     57    *    const math = create(all)
     58    *
     59    *    // define new functions and variables
     60    *    math.import({
     61    *      myvalue: 42,
     62    *      hello: function (name) {
     63    *        return 'hello, ' + name + '!'
     64    *      }
     65    *    })
     66    *
     67    *    // use the imported function and variable
     68    *    math.myvalue * 2               // 84
     69    *    math.hello('user')             // 'hello, user!'
     70    *
     71    *    // import the npm module 'numbers'
     72    *    // (must be installed first with `npm install numbers`)
     73    *    math.import(numbers, {wrap: true})
     74    *
     75    *    math.fibonacci(7) // returns 13
     76    *
     77    * @param {Object | Array} functions  Object with functions to be imported.
     78    * @param {Object} [options]          Import options.
     79    */
     80   function mathImport(functions, options) {
     81     var num = arguments.length;
     82 
     83     if (num !== 1 && num !== 2) {
     84       throw new _ArgumentsError.ArgumentsError('import', num, 1, 2);
     85     }
     86 
     87     if (!options) {
     88       options = {};
     89     }
     90 
     91     function flattenImports(flatValues, value, name) {
     92       if (Array.isArray(value)) {
     93         value.forEach(function (item) {
     94           return flattenImports(flatValues, item);
     95         });
     96       } else if ((0, _typeof2.default)(value) === 'object') {
     97         for (var _name in value) {
     98           if ((0, _object.hasOwnProperty)(value, _name)) {
     99             flattenImports(flatValues, value[_name], _name);
    100           }
    101         }
    102       } else if ((0, _factory.isFactory)(value) || name !== undefined) {
    103         var flatName = (0, _factory.isFactory)(value) ? isTransformFunctionFactory(value) ? value.fn + '.transform' // TODO: this is ugly
    104         : value.fn : name; // we allow importing the same function twice if it points to the same implementation
    105 
    106         if ((0, _object.hasOwnProperty)(flatValues, flatName) && flatValues[flatName] !== value && !options.silent) {
    107           throw new Error('Cannot import "' + flatName + '" twice');
    108         }
    109 
    110         flatValues[flatName] = value;
    111       } else {
    112         if (!options.silent) {
    113           throw new TypeError('Factory, Object, or Array expected');
    114         }
    115       }
    116     }
    117 
    118     var flatValues = {};
    119     flattenImports(flatValues, functions);
    120 
    121     for (var name in flatValues) {
    122       if ((0, _object.hasOwnProperty)(flatValues, name)) {
    123         // console.log('import', name)
    124         var value = flatValues[name];
    125 
    126         if ((0, _factory.isFactory)(value)) {
    127           // we ignore name here and enforce the name of the factory
    128           // maybe at some point we do want to allow overriding it
    129           // in that case we can implement an option overrideFactoryNames: true
    130           _importFactory(value, options);
    131         } else if (isSupportedType(value)) {
    132           _import(name, value, options);
    133         } else {
    134           if (!options.silent) {
    135             throw new TypeError('Factory, Object, or Array expected');
    136           }
    137         }
    138       }
    139     }
    140   }
    141   /**
    142    * Add a property to the math namespace
    143    * @param {string} name
    144    * @param {*} value
    145    * @param {Object} options  See import for a description of the options
    146    * @private
    147    */
    148 
    149 
    150   function _import(name, value, options) {
    151     // TODO: refactor this function, it's to complicated and contains duplicate code
    152     if (options.wrap && typeof value === 'function') {
    153       // create a wrapper around the function
    154       value = _wrap(value);
    155     } // turn a plain function with a typed-function signature into a typed-function
    156 
    157 
    158     if (hasTypedFunctionSignature(value)) {
    159       value = typed(name, (0, _defineProperty2.default)({}, value.signature, value));
    160     }
    161 
    162     if (isTypedFunction(math[name]) && isTypedFunction(value)) {
    163       if (options.override) {
    164         // give the typed function the right name
    165         value = typed(name, value.signatures);
    166       } else {
    167         // merge the existing and typed function
    168         value = typed(math[name], value);
    169       }
    170 
    171       math[name] = value;
    172       delete importedFactories[name];
    173 
    174       _importTransform(name, value);
    175 
    176       math.emit('import', name, function resolver() {
    177         return value;
    178       });
    179       return;
    180     }
    181 
    182     if (math[name] === undefined || options.override) {
    183       math[name] = value;
    184       delete importedFactories[name];
    185 
    186       _importTransform(name, value);
    187 
    188       math.emit('import', name, function resolver() {
    189         return value;
    190       });
    191       return;
    192     }
    193 
    194     if (!options.silent) {
    195       throw new Error('Cannot import "' + name + '": already exists');
    196     }
    197   }
    198 
    199   function _importTransform(name, value) {
    200     if (value && typeof value.transform === 'function') {
    201       math.expression.transform[name] = value.transform;
    202 
    203       if (allowedInExpressions(name)) {
    204         math.expression.mathWithTransform[name] = value.transform;
    205       }
    206     } else {
    207       // remove existing transform
    208       delete math.expression.transform[name];
    209 
    210       if (allowedInExpressions(name)) {
    211         math.expression.mathWithTransform[name] = value;
    212       }
    213     }
    214   }
    215 
    216   function _deleteTransform(name) {
    217     delete math.expression.transform[name];
    218 
    219     if (allowedInExpressions(name)) {
    220       math.expression.mathWithTransform[name] = math[name];
    221     } else {
    222       delete math.expression.mathWithTransform[name];
    223     }
    224   }
    225   /**
    226    * Create a wrapper a round an function which converts the arguments
    227    * to their primitive values (like convert a Matrix to Array)
    228    * @param {Function} fn
    229    * @return {Function} Returns the wrapped function
    230    * @private
    231    */
    232 
    233 
    234   function _wrap(fn) {
    235     var wrapper = function wrapper() {
    236       var args = [];
    237 
    238       for (var i = 0, len = arguments.length; i < len; i++) {
    239         var arg = arguments[i];
    240         args[i] = arg && arg.valueOf();
    241       }
    242 
    243       return fn.apply(math, args);
    244     };
    245 
    246     if (fn.transform) {
    247       wrapper.transform = fn.transform;
    248     }
    249 
    250     return wrapper;
    251   }
    252   /**
    253    * Import an instance of a factory into math.js
    254    * @param {function(scope: object)} factory
    255    * @param {Object} options  See import for a description of the options
    256    * @param {string} [name=factory.name] Optional custom name
    257    * @private
    258    */
    259 
    260 
    261   function _importFactory(factory, options) {
    262     var name = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : factory.fn;
    263 
    264     if ((0, _array.contains)(name, '.')) {
    265       throw new Error('Factory name should not contain a nested path. ' + 'Name: ' + JSON.stringify(name));
    266     }
    267 
    268     var namespace = isTransformFunctionFactory(factory) ? math.expression.transform : math;
    269     var existingTransform = (name in math.expression.transform);
    270     var existing = (0, _object.hasOwnProperty)(namespace, name) ? namespace[name] : undefined;
    271 
    272     var resolver = function resolver() {
    273       // collect all dependencies, handle finding both functions and classes and other special cases
    274       var dependencies = {};
    275       factory.dependencies.map(_factory.stripOptionalNotation).forEach(function (dependency) {
    276         if ((0, _array.contains)(dependency, '.')) {
    277           throw new Error('Factory dependency should not contain a nested path. ' + 'Name: ' + JSON.stringify(dependency));
    278         }
    279 
    280         if (dependency === 'math') {
    281           dependencies.math = math;
    282         } else if (dependency === 'mathWithTransform') {
    283           dependencies.mathWithTransform = math.expression.mathWithTransform;
    284         } else if (dependency === 'classes') {
    285           // special case for json reviver
    286           dependencies.classes = math;
    287         } else {
    288           dependencies[dependency] = math[dependency];
    289         }
    290       });
    291       var instance = /* #__PURE__ */factory(dependencies);
    292 
    293       if (instance && typeof instance.transform === 'function') {
    294         throw new Error('Transforms cannot be attached to factory functions. ' + 'Please create a separate function for it with exports.path="expression.transform"');
    295       }
    296 
    297       if (existing === undefined || options.override) {
    298         return instance;
    299       }
    300 
    301       if (isTypedFunction(existing) && isTypedFunction(instance)) {
    302         // merge the existing and new typed function
    303         return typed(existing, instance);
    304       }
    305 
    306       if (options.silent) {
    307         // keep existing, ignore imported function
    308         return existing;
    309       } else {
    310         throw new Error('Cannot import "' + name + '": already exists');
    311       }
    312     }; // TODO: add unit test with non-lazy factory
    313 
    314 
    315     if (!factory.meta || factory.meta.lazy !== false) {
    316       (0, _object.lazy)(namespace, name, resolver); // FIXME: remove the `if (existing &&` condition again. Can we make sure subset is loaded before subset.transform? (Name collision, and no dependencies between the two)
    317 
    318       if (existing && existingTransform) {
    319         _deleteTransform(name);
    320       } else {
    321         if (isTransformFunctionFactory(factory) || factoryAllowedInExpressions(factory)) {
    322           (0, _object.lazy)(math.expression.mathWithTransform, name, function () {
    323             return namespace[name];
    324           });
    325         }
    326       }
    327     } else {
    328       namespace[name] = resolver(); // FIXME: remove the `if (existing &&` condition again. Can we make sure subset is loaded before subset.transform? (Name collision, and no dependencies between the two)
    329 
    330       if (existing && existingTransform) {
    331         _deleteTransform(name);
    332       } else {
    333         if (isTransformFunctionFactory(factory) || factoryAllowedInExpressions(factory)) {
    334           (0, _object.lazy)(math.expression.mathWithTransform, name, function () {
    335             return namespace[name];
    336           });
    337         }
    338       }
    339     } // TODO: improve factories, store a list with imports instead which can be re-played
    340 
    341 
    342     importedFactories[name] = factory;
    343     math.emit('import', name, resolver);
    344   }
    345   /**
    346    * Check whether given object is a type which can be imported
    347    * @param {Function | number | string | boolean | null | Unit | Complex} object
    348    * @return {boolean}
    349    * @private
    350    */
    351 
    352 
    353   function isSupportedType(object) {
    354     return typeof object === 'function' || typeof object === 'number' || typeof object === 'string' || typeof object === 'boolean' || object === null || (0, _is.isUnit)(object) || (0, _is.isComplex)(object) || (0, _is.isBigNumber)(object) || (0, _is.isFraction)(object) || (0, _is.isMatrix)(object) || Array.isArray(object);
    355   }
    356   /**
    357    * Test whether a given thing is a typed-function
    358    * @param {*} fn
    359    * @return {boolean} Returns true when `fn` is a typed-function
    360    */
    361 
    362 
    363   function isTypedFunction(fn) {
    364     return typeof fn === 'function' && (0, _typeof2.default)(fn.signatures) === 'object';
    365   }
    366 
    367   function hasTypedFunctionSignature(fn) {
    368     return typeof fn === 'function' && typeof fn.signature === 'string';
    369   }
    370 
    371   function allowedInExpressions(name) {
    372     return !(0, _object.hasOwnProperty)(unsafe, name);
    373   }
    374 
    375   function factoryAllowedInExpressions(factory) {
    376     return factory.fn.indexOf('.') === -1 && // FIXME: make checking on path redundant, check on meta data instead
    377     !(0, _object.hasOwnProperty)(unsafe, factory.fn) && (!factory.meta || !factory.meta.isClass);
    378   }
    379 
    380   function isTransformFunctionFactory(factory) {
    381     return factory !== undefined && factory.meta !== undefined && factory.meta.isTransformFunction === true || false;
    382   } // namespaces and functions not available in the parser for safety reasons
    383 
    384 
    385   var unsafe = {
    386     expression: true,
    387     type: true,
    388     docs: true,
    389     error: true,
    390     json: true,
    391     chain: true // chain method not supported. Note that there is a unit chain too.
    392 
    393   };
    394   return mathImport;
    395 }