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
JavaScript
/**
* 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);