UNPKG

mathjs

Version:

Math.js is an extensive math library for JavaScript and Node.js. It features a flexible expression parser and offers an integrated solution to work with numbers, big numbers, complex numbers, units, and matrices.

1,545 lines (1,377 loc) 1.46 MB
/** * math.js * https://github.com/josdejong/mathjs * * Math.js is an extensive math library for JavaScript and Node.js, * It features real and complex numbers, units, matrices, a large set of * mathematical functions, and a flexible expression parser. * * @version 3.2.1 * @date 2016-04-26 * * @license * Copyright (C) 2013-2016 Jos de Jong <wjosdejong@gmail.com> * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy * of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ (function webpackUniversalModuleDefinition(root, factory) { if(typeof exports === 'object' && typeof module === 'object') module.exports = factory(); else if(typeof define === 'function' && define.amd) define([], factory); else if(typeof exports === 'object') exports["math"] = factory(); else root["math"] = factory(); })(this, function() { return /******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ // Check if module is in cache /******/ if(installedModules[moduleId]) /******/ return installedModules[moduleId].exports; /******/ // Create a new module (and put it into the cache) /******/ var module = installedModules[moduleId] = { /******/ exports: {}, /******/ id: moduleId, /******/ loaded: false /******/ }; /******/ // Execute the module function /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ // Flag the module as loaded /******/ module.loaded = true; /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ // expose the modules object (__webpack_modules__) /******/ __webpack_require__.m = modules; /******/ // expose the module cache /******/ __webpack_require__.c = installedModules; /******/ // __webpack_public_path__ /******/ __webpack_require__.p = ""; /******/ // Load entry module and return exports /******/ return __webpack_require__(0); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ /***/ function(module, exports, __webpack_require__) { var core = __webpack_require__(1); /** * math.js factory function. Creates a new instance of math.js * * @param {Object} [config] Available configuration options: * {number} epsilon * Minimum relative difference between two * compared values, used by all comparison functions. * {string} matrix * A string 'matrix' (default) or 'array'. * {string} number * A string 'number' (default), 'bignumber', or * 'fraction' * {number} precision * The number of significant digits for BigNumbers. * Not applicable for Numbers. * {boolean} predictable * Predictable output type of functions. When true, * output type depends only on the input types. When * false (default), output type can vary depending * on input values. For example `math.sqrt(-2)` * returns `NaN` when predictable is false, and * returns `complex('2i')` when true. */ function create (config) { // create a new math.js instance var math = core.create(config); math.create = create; // import data types, functions, constants, expression parser, etc. math['import'](__webpack_require__(13)); return math; } // return a new instance of math.js module.exports = create(); /***/ }, /* 1 */ /***/ function(module, exports, __webpack_require__) { module.exports = __webpack_require__(2); /***/ }, /* 2 */ /***/ function(module, exports, __webpack_require__) { var isFactory = __webpack_require__(3).isFactory; var deepExtend = __webpack_require__(3).deepExtend; var typedFactory = __webpack_require__(4); var emitter = __webpack_require__(8); var importFactory = __webpack_require__(10); var configFactory = __webpack_require__(12); /** * Math.js core. Creates a new, empty math.js instance * @param {Object} [options] Available options: * {number} epsilon * Minimum relative difference between two * compared values, used by all comparison functions. * {string} matrix * A string 'Matrix' (default) or 'Array'. * {string} number * A string 'number' (default), 'BigNumber', or 'Fraction' * {number} precision * The number of significant digits for BigNumbers. * Not applicable for Numbers. * {boolean} predictable * Predictable output type of functions. When true, * output type depends only on the input types. When * false (default), output type can vary depending * on input values. For example `math.sqrt(-2)` * returns `NaN` when predictable is false, and * returns `complex('2i')` when true. * @returns {Object} Returns a bare-bone math.js instance containing * functions: * - `import` to add new functions * - `config` to change configuration * - `on`, `off`, `once`, `emit` for events */ exports.create = function create (options) { // simple test for ES5 support if (typeof Object.create !== 'function') { throw new Error('ES5 not supported by this JavaScript engine. ' + 'Please load the es5-shim and es5-sham library for compatibility.'); } // cached factories and instances var factories = []; var instances = []; // create a namespace for the mathjs instance, and attach emitter functions var math = emitter.mixin({}); math.type = {}; math.expression = { transform: Object.create(math) }; // create a new typed instance math.typed = typedFactory.create(math.type); // create configuration options. These are private var _config = { // minimum relative difference between two compared values, // used by all comparison functions epsilon: 1e-12, // type of default matrix output. Choose 'matrix' (default) or 'array' matrix: 'Matrix', // type of default number output. Choose 'number' (default) 'BigNumber', or 'Fraction number: 'number', // number of significant digits in BigNumbers precision: 64, // predictable output type of functions. When true, output type depends only // on the input types. When false (default), output type can vary depending // on input values. For example `math.sqrt(-2)` returns `NaN` when // predictable is false, and returns `complex('2i')` when true. predictable: false }; /** * Load a function or data type from a factory. * If the function or data type already exists, the existing instance is * returned. * @param {{type: string, name: string, factory: Function}} factory * @returns {*} */ function load (factory) { if (!isFactory(factory)) { throw new Error('Factory object with properties `type`, `name`, and `factory` expected'); } var index = factories.indexOf(factory); var instance; if (index === -1) { // doesn't yet exist if (factory.math === true) { // pass with math namespace instance = factory.factory(math.type, _config, load, math.typed, math); } else { instance = factory.factory(math.type, _config, load, math.typed); } // append to the cache factories.push(factory); instances.push(instance); } else { // already existing function, return the cached instance instance = instances[index]; } return instance; } // load the import and config functions math['import'] = load(importFactory); math['config'] = load(configFactory); // apply options if (options) { math.config(options); } return math; }; /***/ }, /* 3 */ /***/ function(module, exports) { 'use strict'; /** * Clone an object * * clone(x) * * Can clone any primitive type, array, and object. * If x has a function clone, this function will be invoked to clone the object. * * @param {*} x * @return {*} clone */ exports.clone = function clone(x) { var type = typeof x; // immutable primitive types if (type === 'number' || type === 'string' || type === 'boolean' || x === null || x === undefined) { return x; } // use clone function of the object when available if (typeof x.clone === 'function') { return x.clone(); } // array if (Array.isArray(x)) { return x.map(function (value) { return clone(value); }); } if (x instanceof Number) return new Number(x.valueOf()); if (x instanceof String) return new String(x.valueOf()); if (x instanceof Boolean) return new Boolean(x.valueOf()); if (x instanceof Date) return new Date(x.valueOf()); if (x && x.isBigNumber === true) return x; // bignumbers are immutable if (x instanceof RegExp) throw new TypeError('Cannot clone ' + x); // TODO: clone a RegExp // object var m = {}; for (var key in x) { if (x.hasOwnProperty(key)) { m[key] = clone(x[key]); } } return m; }; /** * Extend object a with the properties of object b * @param {Object} a * @param {Object} b * @return {Object} a */ exports.extend = function(a, b) { for (var prop in b) { if (b.hasOwnProperty(prop)) { a[prop] = b[prop]; } } return a; }; /** * Deep extend an object a with the properties of object b * @param {Object} a * @param {Object} b * @returns {Object} */ exports.deepExtend = function deepExtend (a, b) { // TODO: add support for Arrays to deepExtend if (Array.isArray(b)) { throw new TypeError('Arrays are not supported by deepExtend'); } for (var prop in b) { if (b.hasOwnProperty(prop)) { if (b[prop] && b[prop].constructor === Object) { if (a[prop] === undefined) { a[prop] = {}; } if (a[prop].constructor === Object) { deepExtend(a[prop], b[prop]); } else { a[prop] = b[prop]; } } else if (Array.isArray(b[prop])) { throw new TypeError('Arrays are not supported by deepExtend'); } else { a[prop] = b[prop]; } } } return a; }; /** * Deep test equality of all fields in two pairs of arrays or objects. * @param {Array | Object} a * @param {Array | Object} b * @returns {boolean} */ exports.deepEqual = function deepEqual (a, b) { var prop, i, len; if (Array.isArray(a)) { if (!Array.isArray(b)) { return false; } if (a.length != b.length) { return false; } for (i = 0, len = a.length; i < len; i++) { if (!exports.deepEqual(a[i], b[i])) { return false; } } return true; } else if (a instanceof Object) { if (Array.isArray(b) || !(b instanceof Object)) { return false; } for (prop in a) { //noinspection JSUnfilteredForInLoop if (!exports.deepEqual(a[prop], b[prop])) { return false; } } for (prop in b) { //noinspection JSUnfilteredForInLoop if (!exports.deepEqual(a[prop], b[prop])) { return false; } } return true; } else { return (typeof a === typeof b) && (a == b); } }; /** * Test whether the current JavaScript engine supports Object.defineProperty * @returns {boolean} returns true if supported */ exports.canDefineProperty = function () { // test needed for broken IE8 implementation try { if (Object.defineProperty) { Object.defineProperty({}, 'x', { get: function () {} }); return true; } } catch (e) {} return false; }; /** * Attach a lazy loading property to a constant. * The given function `fn` is called once when the property is first requested. * On older browsers (<IE8), the function will fall back to direct evaluation * of the properties value. * @param {Object} object Object where to add the property * @param {string} prop Property name * @param {Function} fn Function returning the property value. Called * without arguments. */ exports.lazy = function (object, prop, fn) { if (exports.canDefineProperty()) { var _uninitialized = true; var _value; Object.defineProperty(object, prop, { get: function () { if (_uninitialized) { _value = fn(); _uninitialized = false; } return _value; }, set: function (value) { _value = value; _uninitialized = false; }, configurable: true, enumerable: true }); } else { // fall back to immediate evaluation object[prop] = fn(); } }; /** * Traverse a path into an object. * When a namespace is missing, it will be created * @param {Object} object * @param {string} path A dot separated string like 'name.space' * @return {Object} Returns the object at the end of the path */ exports.traverse = function(object, path) { var obj = object; if (path) { var names = path.split('.'); for (var i = 0; i < names.length; i++) { var name = names[i]; if (!(name in obj)) { obj[name] = {}; } obj = obj[name]; } } return obj; }; /** * Test whether an object is a factory. a factory has fields: * * - factory: function (type: Object, config: Object, load: function, typed: function [, math: Object]) (required) * - name: string (optional) * - path: string A dot separated path (optional) * - math: boolean If true (false by default), the math namespace is passed * as fifth argument of the factory function * * @param {*} object * @returns {boolean} */ exports.isFactory = function (object) { return object && typeof object.factory === 'function'; }; /***/ }, /* 4 */ /***/ function(module, exports, __webpack_require__) { var typedFunction = __webpack_require__(5); var digits = __webpack_require__(6).digits; // returns a new instance of typed-function var createTyped = function () { // initially, return the original instance of typed-function // consecutively, return a new instance from typed.create. createTyped = typedFunction.create; return typedFunction; }; /** * Factory function for creating a new typed instance * @param {Object} type Object with data types like Complex and BigNumber * @returns {Function} */ exports.create = function create(type) { // TODO: typed-function must be able to silently ignore signatures with unknown data types // get a new instance of typed-function var typed = createTyped(); // define all types. The order of the types determines in which order function // arguments are type-checked (so for performance it's important to put the // most used types first). typed.types = [ { name: 'number', test: function (x) { return typeof x === 'number'; } }, { name: 'Complex', test: function (x) { return x && x.isComplex; } }, { name: 'BigNumber', test: function (x) { return x && x.isBigNumber; } }, { name: 'Fraction', test: function (x) { return x && x.isFraction; } }, { name: 'Unit', test: function (x) { return x && x.isUnit; } }, { name: 'string', test: function (x) { return typeof x === 'string'; } }, { name: 'Array', test: Array.isArray }, { name: 'Matrix', test: function (x) { return x && x.isMatrix; } }, { name: 'DenseMatrix', test: function (x) { return x && x.isDenseMatrix; } }, { name: 'SparseMatrix', test: function (x) { return x && x.isSparseMatrix; } }, { name: 'ImmutableDenseMatrix', test: function (x) { return x && x.isImmutableDenseMatrix; } }, { name: 'Range', test: function (x) { return x && x.isRange; } }, { name: 'Index', test: function (x) { return x && x.isIndex; } }, { name: 'boolean', test: function (x) { return typeof x === 'boolean'; } }, { name: 'ResultSet', test: function (x) { return x && x.isResultSet; } }, { name: 'Help', test: function (x) { return x && x.isHelp; } }, { name: 'function', test: function (x) { return typeof x === 'function';} }, { name: 'Date', test: function (x) { return x instanceof Date; } }, { name: 'RegExp', test: function (x) { return x instanceof RegExp; } }, { name: 'Object', test: function (x) { return typeof x === 'object'; } }, { name: 'null', test: function (x) { return x === null; } }, { name: 'undefined', test: function (x) { return x === undefined; } } ]; // TODO: add conversion from BigNumber to number? typed.conversions = [ { from: 'number', to: 'BigNumber', convert: function (x) { // note: conversion from number to BigNumber can fail if x has >15 digits if (digits(x) > 15) { throw new TypeError('Cannot implicitly convert a number with >15 significant digits to BigNumber ' + '(value: ' + x + '). ' + 'Use function bignumber(x) to convert to BigNumber.'); } return new type.BigNumber(x); } }, { from: 'number', to: 'Complex', convert: function (x) { return new type.Complex(x, 0); } }, { from: 'number', to: 'string', convert: function (x) { return x + ''; } }, { from: 'BigNumber', to: 'Complex', convert: function (x) { return new type.Complex(x.toNumber(), 0); } }, { from: 'Fraction', to: 'Complex', convert: function (x) { return new type.Complex(x.valueOf(), 0); } }, { from: 'number', to: 'Fraction', convert: function (x) { if (digits(x) > 15) { throw new TypeError('Cannot implicitly convert a number with >15 significant digits to Fraction ' + '(value: ' + x + '). ' + 'Use function fraction(x) to convert to Fraction.'); } return new type.Fraction(x); } }, { // FIXME: add conversion from Fraction to number, for example for `sqrt(fraction(1,3))` // from: 'Fraction', // to: 'number', // convert: function (x) { // return x.valueOf(); // } //}, { from: 'string', to: 'number', convert: function (x) { var n = Number(x); if (isNaN(n)) { throw new Error('Cannot convert "' + x + '" to a number'); } return n; } }, { from: 'boolean', to: 'number', convert: function (x) { return +x; } }, { from: 'boolean', to: 'BigNumber', convert: function (x) { return new type.BigNumber(+x); } }, { from: 'boolean', to: 'Fraction', convert: function (x) { return new type.Fraction(+x); } }, { from: 'boolean', to: 'string', convert: function (x) { return +x; } }, { from: 'null', to: 'number', convert: function () { return 0; } }, { from: 'null', to: 'string', convert: function () { return 'null'; } }, { from: 'null', to: 'BigNumber', convert: function () { return new type.BigNumber(0); } }, { from: 'null', to: 'Fraction', convert: function () { return new type.Fraction(0); } }, { from: 'Array', to: 'Matrix', convert: function (array) { // TODO: how to decide on the right type of matrix to create? return new type.DenseMatrix(array); } }, { from: 'Matrix', to: 'Array', convert: function (matrix) { return matrix.valueOf(); } } ]; return typed; }; /***/ }, /* 5 */ /***/ function(module, exports, __webpack_require__) { var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/** * typed-function * * Type checking for JavaScript functions * * https://github.com/josdejong/typed-function */ 'use strict'; (function (root, factory) { if (true) { // AMD. Register as an anonymous module. !(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); } else if (typeof exports === 'object') { // OldNode. Does not work with strict CommonJS, but // only CommonJS-like environments that support module.exports, // like OldNode. module.exports = factory(); } else { // Browser globals (root is window) root.typed = factory(); } }(this, function () { // factory function to create a new instance of typed-function // TODO: allow passing configuration, types, tests via the factory function function create() { /** * Get a type test function for a specific data type * @param {string} name Name of a data type like 'number' or 'string' * @returns {Function(obj: *) : boolean} Returns a type testing function. * Throws an error for an unknown type. */ function getTypeTest(name) { var test; for (var i = 0; i < typed.types.length; i++) { var entry = typed.types[i]; if (entry.name === name) { test = entry.test; break; } } if (!test) { var hint; for (i = 0; i < typed.types.length; i++) { entry = typed.types[i]; if (entry.name.toLowerCase() == name.toLowerCase()) { hint = entry.name; break; } } throw new Error('Unknown type "' + name + '"' + (hint ? ('. Did you mean "' + hint + '"?') : '')); } return test; } /** * Retrieve the function name from a set of functions, and check * whether the name of all functions match (if given) * @param {Array.<function>} fns */ function getName (fns) { var name = ''; for (var i = 0; i < fns.length; i++) { var fn = fns[i]; // merge function name when this is a typed function if (fn.signatures && fn.name != '') { if (name == '') { name = fn.name; } else if (name != fn.name) { var err = new Error('Function names do not match (expected: ' + name + ', actual: ' + fn.name + ')'); err.data = { actual: fn.name, expected: name }; throw err; } } } return name; } /** * Create an ArgumentsError. Creates messages like: * * Unexpected type of argument (expected: ..., actual: ..., index: ...) * Too few arguments (expected: ..., index: ...) * Too many arguments (expected: ..., actual: ...) * * @param {String} fn Function name * @param {number} argCount Number of arguments * @param {Number} index Current argument index * @param {*} actual Current argument * @param {string} [expected] An optional, comma separated string with * expected types on given index * @extends Error */ function createError(fn, argCount, index, actual, expected) { var actualType = getTypeOf(actual); var _expected = expected ? expected.split(',') : null; var _fn = (fn || 'unnamed'); var anyType = _expected && contains(_expected, 'any'); var message; var data = { fn: fn, index: index, actual: actual, expected: _expected }; if (_expected) { if (argCount > index && !anyType) { // unexpected type message = 'Unexpected type of argument in function ' + _fn + ' (expected: ' + _expected.join(' or ') + ', actual: ' + actualType + ', index: ' + index + ')'; } else { // too few arguments message = 'Too few arguments in function ' + _fn + ' (expected: ' + _expected.join(' or ') + ', index: ' + index + ')'; } } else { // too many arguments message = 'Too many arguments in function ' + _fn + ' (expected: ' + index + ', actual: ' + argCount + ')' } var err = new TypeError(message); err.data = data; return err; } /** * Collection with function references (local shortcuts to functions) * @constructor * @param {string} [name='refs'] Optional name for the refs, used to generate * JavaScript code */ function Refs(name) { this.name = name || 'refs'; this.categories = {}; } /** * Add a function reference. * @param {Function} fn * @param {string} [category='fn'] A function category, like 'fn' or 'signature' * @returns {string} Returns the function name, for example 'fn0' or 'signature2' */ Refs.prototype.add = function (fn, category) { var cat = category || 'fn'; if (!this.categories[cat]) this.categories[cat] = []; var index = this.categories[cat].indexOf(fn); if (index == -1) { index = this.categories[cat].length; this.categories[cat].push(fn); } return cat + index; }; /** * Create code lines for all function references * @returns {string} Returns the code containing all function references */ Refs.prototype.toCode = function () { var code = []; var path = this.name + '.categories'; var categories = this.categories; for (var cat in categories) { if (categories.hasOwnProperty(cat)) { var category = categories[cat]; for (var i = 0; i < category.length; i++) { code.push('var ' + cat + i + ' = ' + path + '[\'' + cat + '\'][' + i + '];'); } } } return code.join('\n'); }; /** * A function parameter * @param {string | string[] | Param} types A parameter type like 'string', * 'number | boolean' * @param {boolean} [varArgs=false] Variable arguments if true * @constructor */ function Param(types, varArgs) { // parse the types, can be a string with types separated by pipe characters | if (typeof types === 'string') { // parse variable arguments operator (ellipses '...number') var _types = types.trim(); var _varArgs = _types.substr(0, 3) === '...'; if (_varArgs) { _types = _types.substr(3); } if (_types === '') { this.types = ['any']; } else { this.types = _types.split('|'); for (var i = 0; i < this.types.length; i++) { this.types[i] = this.types[i].trim(); } } } else if (Array.isArray(types)) { this.types = types; } else if (types instanceof Param) { return types.clone(); } else { throw new Error('String or Array expected'); } // can hold a type to which to convert when handling this parameter this.conversions = []; // TODO: implement better API for conversions, be able to add conversions via constructor (support a new type Object?) // variable arguments this.varArgs = _varArgs || varArgs || false; // check for any type arguments this.anyType = this.types.indexOf('any') !== -1; } /** * Order Params * any type ('any') will be ordered last, and object as second last (as other * types may be an object as well, like Array). * * @param {Param} a * @param {Param} b * @returns {number} Returns 1 if a > b, -1 if a < b, and else 0. */ Param.compare = function (a, b) { // TODO: simplify parameter comparison, it's a mess if (a.anyType) return 1; if (b.anyType) return -1; if (contains(a.types, 'Object')) return 1; if (contains(b.types, 'Object')) return -1; if (a.hasConversions()) { if (b.hasConversions()) { var i, ac, bc; for (i = 0; i < a.conversions.length; i++) { if (a.conversions[i] !== undefined) { ac = a.conversions[i]; break; } } for (i = 0; i < b.conversions.length; i++) { if (b.conversions[i] !== undefined) { bc = b.conversions[i]; break; } } return typed.conversions.indexOf(ac) - typed.conversions.indexOf(bc); } else { return 1; } } else { if (b.hasConversions()) { return -1; } else { // both params have no conversions var ai, bi; for (i = 0; i < typed.types.length; i++) { if (typed.types[i].name === a.types[0]) { ai = i; break; } } for (i = 0; i < typed.types.length; i++) { if (typed.types[i].name === b.types[0]) { bi = i; break; } } return ai - bi; } } }; /** * Test whether this parameters types overlap an other parameters types. * @param {Param} other * @return {boolean} Returns true when there are conflicting types */ Param.prototype.overlapping = function (other) { for (var i = 0; i < this.types.length; i++) { if (contains(other.types, this.types[i])) { return true; } } return false; }; /** * Create a clone of this param * @returns {Param} Returns a cloned version of this param */ Param.prototype.clone = function () { var param = new Param(this.types.slice(), this.varArgs); param.conversions = this.conversions.slice(); return param; }; /** * Test whether this parameter contains conversions * @returns {boolean} Returns true if the parameter contains one or * multiple conversions. */ Param.prototype.hasConversions = function () { return this.conversions.length > 0; }; /** * Tests whether this parameters contains any of the provided types * @param {Object} types A Map with types, like {'number': true} * @returns {boolean} Returns true when the parameter contains any * of the provided types */ Param.prototype.contains = function (types) { for (var i = 0; i < this.types.length; i++) { if (types[this.types[i]]) { return true; } } return false; }; /** * Return a string representation of this params types, like 'string' or * 'number | boolean' or '...number' * @param {boolean} [toConversion] If true, the returned types string * contains the types where the parameter * will convert to. If false (default) * the "from" types are returned * @returns {string} */ Param.prototype.toString = function (toConversion) { var types = []; var keys = {}; for (var i = 0; i < this.types.length; i++) { var conversion = this.conversions[i]; var type = toConversion && conversion ? conversion.to : this.types[i]; if (!(type in keys)) { keys[type] = true; types.push(type); } } return (this.varArgs ? '...' : '') + types.join('|'); }; /** * A function signature * @param {string | string[] | Param[]} params * Array with the type(s) of each parameter, * or a comma separated string with types * @param {Function} fn The actual function * @constructor */ function Signature(params, fn) { var _params; if (typeof params === 'string') { _params = (params !== '') ? params.split(',') : []; } else if (Array.isArray(params)) { _params = params; } else { throw new Error('string or Array expected'); } this.params = new Array(_params.length); for (var i = 0; i < _params.length; i++) { var param = new Param(_params[i]); this.params[i] = param; if (i === _params.length - 1) { // the last argument this.varArgs = param.varArgs; } else { // non-last argument if (param.varArgs) { throw new SyntaxError('Unexpected variable arguments operator "..."'); } } } this.fn = fn; } /** * Create a clone of this signature * @returns {Signature} Returns a cloned version of this signature */ Signature.prototype.clone = function () { return new Signature(this.params.slice(), this.fn); }; /** * Expand a signature: split params with union types in separate signatures * For example split a Signature "string | number" into two signatures. * @return {Signature[]} Returns an array with signatures (at least one) */ Signature.prototype.expand = function () { var signatures = []; function recurse(signature, path) { if (path.length < signature.params.length) { var i, newParam, conversion; var param = signature.params[path.length]; if (param.varArgs) { // a variable argument. do not split the types in the parameter newParam = param.clone(); // add conversions to the parameter // recurse for all conversions for (i = 0; i < typed.conversions.length; i++) { conversion = typed.conversions[i]; if (!contains(param.types, conversion.from) && contains(param.types, conversion.to)) { var j = newParam.types.length; newParam.types[j] = conversion.from; newParam.conversions[j] = conversion; } } recurse(signature, path.concat(newParam)); } else { // split each type in the parameter for (i = 0; i < param.types.length; i++) { recurse(signature, path.concat(new Param(param.types[i]))); } // recurse for all conversions for (i = 0; i < typed.conversions.length; i++) { conversion = typed.conversions[i]; if (!contains(param.types, conversion.from) && contains(param.types, conversion.to)) { newParam = new Param(conversion.from); newParam.conversions[0] = conversion; recurse(signature, path.concat(newParam)); } } } } else { signatures.push(new Signature(path, signature.fn)); } } recurse(this, []); return signatures; }; /** * Compare two signatures. * * When two params are equal and contain conversions, they will be sorted * by lowest index of the first conversions. * * @param {Signature} a * @param {Signature} b * @returns {number} Returns 1 if a > b, -1 if a < b, and else 0. */ Signature.compare = function (a, b) { if (a.params.length > b.params.length) return 1; if (a.params.length < b.params.length) return -1; // count the number of conversions var i; var len = a.params.length; // a and b have equal amount of params var ac = 0; var bc = 0; for (i = 0; i < len; i++) { if (a.params[i].hasConversions()) ac++; if (b.params[i].hasConversions()) bc++; } if (ac > bc) return 1; if (ac < bc) return -1; // compare the order per parameter for (i = 0; i < a.params.length; i++) { var cmp = Param.compare(a.params[i], b.params[i]); if (cmp !== 0) { return cmp; } } return 0; }; /** * Test whether any of the signatures parameters has conversions * @return {boolean} Returns true when any of the parameters contains * conversions. */ Signature.prototype.hasConversions = function () { for (var i = 0; i < this.params.length; i++) { if (this.params[i].hasConversions()) { return true; } } return false; }; /** * Test whether this signature should be ignored. * Checks whether any of the parameters contains a type listed in * typed.ignore * @return {boolean} Returns true when the signature should be ignored */ Signature.prototype.ignore = function () { // create a map with ignored types var types = {}; for (var i = 0; i < typed.ignore.length; i++) { types[typed.ignore[i]] = true; } // test whether any of the parameters contains this type for (i = 0; i < this.params.length; i++) { if (this.params[i].contains(types)) { return true; } } return false; }; /** * Generate the code to invoke this signature * @param {Refs} refs * @param {string} prefix * @returns {string} Returns code */ Signature.prototype.toCode = function (refs, prefix) { var code = []; var args = new Array(this.params.length); for (var i = 0; i < this.params.length; i++) { var param = this.params[i]; var conversion = param.conversions[0]; if (param.varArgs) { args[i] = 'varArgs'; } else if (conversion) { args[i] = refs.add(conversion.convert, 'convert') + '(arg' + i + ')'; } else { args[i] = 'arg' + i; } } var ref = this.fn ? refs.add(this.fn, 'signature') : undefined; if (ref) { return prefix + 'return ' + ref + '(' + args.join(', ') + '); // signature: ' + this.params.join(', '); } return code.join('\n'); }; /** * Return a string representation of the signature * @returns {string} */ Signature.prototype.toString = function () { return this.params.join(', '); }; /** * A group of signatures with the same parameter on given index * @param {Param[]} path * @param {Signature} [signature] * @param {Node[]} childs * @constructor */ function Node(path, signature, childs) { this.path = path || []; this.param = path[path.length - 1] || null; this.signature = signature || null; this.childs = childs || []; } /** * Generate code for this group of signatures * @param {Refs} refs * @param {string} prefix * @param {Node | undefined} [anyType] Sibling of this node with any type parameter * @returns {string} Returns the code as string */ Node.prototype.toCode = function (refs, prefix, anyType) { // TODO: split this function in multiple functions, it's too large var code = []; if (this.param) { var index = this.path.length - 1; var conversion = this.param.conversions[0]; var comment = '// type: ' + (conversion ? (conversion.from + ' (convert to ' + conversion.to + ')') : this.param); // non-root node (path is non-empty) if (this.param.varArgs) { if (this.param.anyType) { // variable arguments with any type code.push(prefix + 'if (arguments.length > ' + index + ') {'); code.push(prefix + ' var varArgs = [];'); code.push(prefix + ' for (var i = ' + index + '; i < arguments.length; i++) {'); code.push(prefix + ' varArgs.push(arguments[i]);'); code.push(prefix + ' }'); code.push(this.signature.toCode(refs, prefix + ' ')); code.push(prefix + '}'); } else { // variable arguments with a fixed type var getTests = function (types, arg) { var tests = []; for (var i = 0; i < types.length; i++) { tests[i] = refs.add(getTypeTest(types[i]), 'test') + '(' + arg + ')'; } return tests.join(' || '); }.bind(this); var allTypes = this.param.types; var exactTypes = []; for (var i = 0; i < allTypes.length; i++) { if (this.param.conversions[i] === undefined) { exactTypes.push(allTypes[i]); } } code.push(prefix + 'if (' + getTests(allTypes, 'arg' + index) + ') { ' + comment); code.push(prefix + ' var varArgs = [arg' + index + '];'); code.push(prefix + ' for (var i = ' + (index + 1) + '; i < arguments.length; i++) {'); code.push(prefix + ' if (' + getTests(exactTypes, 'arguments[i]') + ') {'); code.push(prefix + ' varArgs.push(arguments[i]);'); for (var i = 0; i < allTypes.length; i++) { var conversion_i = this.param.conversions[i]; if (conversion_i) { var test = refs.add(getTypeTest(allTypes[i]), 'test'); var convert = refs.add(conversion_i.convert, 'convert'); code.push(prefix + ' }'); code.push(prefix + ' else if (' + test + '(arguments[i])) {'); code.push(prefix + ' varArgs.push(' + convert + '(arguments[i]));'); } } code.push(prefix + ' } else {'); code.push(prefix + ' throw createError(name, arguments.length, i, arguments[i], \'' + exactTypes.join(',') + '\');'); code.push(prefix + ' }'); code.push(prefix + ' }'); code.push(this.signature.toCode(refs, prefix + ' ')); code.push(prefix + '}'); } } else { if (this.param.anyType) { // any type code.push(prefix + '// type: any'); code.push(this._innerCode(refs, prefix, anyType)); } else { // regular type var type = this.param.types[0]; var test = type !== 'any' ? refs.add(getTypeTest(type), 'test') : null; code.push(prefix + 'if (' + test + '(arg' + index + ')) { ' + comment); code.push(this._innerCode(refs, prefix + ' ', anyType)); code.push(prefix + '}'); } } } else { // root node (path is empty) code.push(this._innerCode(refs, prefix, anyType)); } return code.join('\n'); }; /** * Generate inner code for this group of signatures. * This is a helper function of Node.prototype.toCode * @param {Refs} refs * @param {string} prefix * @param {Node | undefined} [anyType] Sibling of this node with any type parameter * @returns {string} Returns the inner code as string * @private */ Node.prototype._innerCode = function (refs, prefix, anyType) { var code = []; var i; if (this.signature) { code.push(prefix + 'if (arguments.length === ' + this.path.length + ') {'); code.push(this.signature.toCode(refs, prefix + ' ')); code.push(prefix + '}'); } var nextAnyType; for (i = 0; i < this.childs.length; i++) { if (this.childs[i].param.anyType) { nextAnyType = this.childs[i]; break; } } for (i = 0; i < this.childs.length; i++) { code.push(this.childs[i].toCode(refs, prefix, nextAnyType)); } if (anyType && !this.param.anyType) { code.push(anyType.toCode(refs, prefix, nextAnyType)); } var exceptions = this._exceptions(refs, prefix); if (exceptions) { code.push(exceptions); } return code.join('\n'); }; /** * Generate code to throw exceptions * @param {Refs} refs * @param {string} prefix * @returns {string} Returns the inner code as string * @private */ Node.prototype._exceptions = function (refs, prefix) { var index = this.path.length; if (this.childs.length === 0) { // TODO: can this condition be simplified? (we have a fall-through here) return [ prefix + 'if (arguments.length > ' + index + ') {', prefix + ' throw createError(name, arguments.length, ' + index + ', arguments[' + index + ']);', prefix + '}' ].join('\n'); } else { var keys = {}; var types = []; for (var i = 0; i < this.childs.length; i++) { var node = this.childs[i]; if (node.param) { for (var j = 0; j < node.param.types.length; j++) { var type = node.param.types[j]; if (!(type in keys) && !node.param.conversions[j]) { keys[type] = true; types.push(type); } } } } return prefix + 'throw createError(name, arguments.length, ' + index + ', arguments[' + index + '], \'' + types.join(',') + '\');'; } }; /** * Split all raw signatures into an array with expanded Signatures * @param {Object.<string, Function>} rawSignatures * @return {Signature[]} Returns an array with expanded signatures */ function parseSignatures(rawSignatures) { // FIXME: need to have deterministic ordering of signatures, do not create via object var signature; var keys = {}; var signatures = []; var i; for (var types in rawSignatures) { if (rawSignatures.hasOwnProperty(types)) { var fn = rawSignatures[types]; signature = new Signature(types, fn); if (signature.ignore()) { continue; } var expanded = signature.expand(); for (i = 0; i < expanded.length; i++) { var signature_i = expanded[i]; var key = signature_i.toString(); var existing = keys[key]; if (!existing) { keys[key] = signature_i; } else { var cmp = Signature.compare(signature_i, existing); if (cmp < 0) { // override if sorted first keys[key] = signature_i; } else if (cmp === 0) { throw new Error('Signature "' + key + '" is defined twice'); } // else: just ignore } } } } // convert from map to array for (key in keys) { if (keys.hasOwnProperty(key)) { signatures.push(keys[key]); } } // order the signatures signatures.sort(function (a, b) { return Signature.compare(a, b);