quanta_sim
Version:
A comprehensive quantum computing simulator library with support for quantum circuits, gates, measurements, and classical conditional operations
1,431 lines (1,351 loc) • 675 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.QuantaSim = {}));
})(this, (function (exports) { 'use strict';
function _extends() {
return _extends = Object.assign ? Object.assign.bind() : function (n) {
for (var e = 1; e < arguments.length; e++) {
var t = arguments[e];
for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]);
}
return n;
}, _extends.apply(null, arguments);
}
var DEFAULT_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(-4)` returns `complex('2i')` when
// predictable is false, and returns `NaN` when true.
predictable: false,
// random seed for seeded pseudo random number generation
// null = randomly seed
randomSeed: null
};
// type checks for all known types
//
// note that:
//
// - check by duck-typing on a property like `isUnit`, instead of checking instanceof.
// instanceof cannot be used because that would not allow to pass data from
// one instance of math.js to another since each has it's own instance of Unit.
// - check the `isUnit` property via the constructor, so there will be no
// matches for "fake" instances like plain objects with a property `isUnit`.
// That is important for security reasons.
// - It must not be possible to override the type checks used internally,
// for security reasons, so these functions are not exposed in the expression
// parser.
function isNumber(x) {
return typeof x === 'number';
}
function isBigNumber(x) {
if (!x || typeof x !== 'object' || typeof x.constructor !== 'function') {
return false;
}
if (x.isBigNumber === true && typeof x.constructor.prototype === 'object' && x.constructor.prototype.isBigNumber === true) {
return true;
}
if (typeof x.constructor.isDecimal === 'function' && x.constructor.isDecimal(x) === true) {
return true;
}
return false;
}
function isComplex(x) {
return x && typeof x === 'object' && Object.getPrototypeOf(x).isComplex === true || false;
}
function isFraction(x) {
return x && typeof x === 'object' && Object.getPrototypeOf(x).isFraction === true || false;
}
function isUnit(x) {
return x && x.constructor.prototype.isUnit === true || false;
}
function isString(x) {
return typeof x === 'string';
}
var isArray = Array.isArray;
function isMatrix(x) {
return x && x.constructor.prototype.isMatrix === true || false;
}
/**
* Test whether a value is a collection: an Array or Matrix
* @param {*} x
* @returns {boolean} isCollection
*/
function isCollection(x) {
return Array.isArray(x) || isMatrix(x);
}
function isDenseMatrix(x) {
return x && x.isDenseMatrix && x.constructor.prototype.isMatrix === true || false;
}
function isSparseMatrix(x) {
return x && x.isSparseMatrix && x.constructor.prototype.isMatrix === true || false;
}
function isRange(x) {
return x && x.constructor.prototype.isRange === true || false;
}
function isIndex(x) {
return x && x.constructor.prototype.isIndex === true || false;
}
function isBoolean(x) {
return typeof x === 'boolean';
}
function isResultSet(x) {
return x && x.constructor.prototype.isResultSet === true || false;
}
function isHelp(x) {
return x && x.constructor.prototype.isHelp === true || false;
}
function isFunction(x) {
return typeof x === 'function';
}
function isDate(x) {
return x instanceof Date;
}
function isRegExp(x) {
return x instanceof RegExp;
}
function isObject(x) {
return !!(x && typeof x === 'object' && x.constructor === Object && !isComplex(x) && !isFraction(x));
}
function isNull(x) {
return x === null;
}
function isUndefined(x) {
return x === undefined;
}
function isAccessorNode(x) {
return x && x.isAccessorNode === true && x.constructor.prototype.isNode === true || false;
}
function isArrayNode(x) {
return x && x.isArrayNode === true && x.constructor.prototype.isNode === true || false;
}
function isAssignmentNode(x) {
return x && x.isAssignmentNode === true && x.constructor.prototype.isNode === true || false;
}
function isBlockNode(x) {
return x && x.isBlockNode === true && x.constructor.prototype.isNode === true || false;
}
function isConditionalNode(x) {
return x && x.isConditionalNode === true && x.constructor.prototype.isNode === true || false;
}
function isConstantNode(x) {
return x && x.isConstantNode === true && x.constructor.prototype.isNode === true || false;
}
function isFunctionAssignmentNode(x) {
return x && x.isFunctionAssignmentNode === true && x.constructor.prototype.isNode === true || false;
}
function isFunctionNode(x) {
return x && x.isFunctionNode === true && x.constructor.prototype.isNode === true || false;
}
function isIndexNode(x) {
return x && x.isIndexNode === true && x.constructor.prototype.isNode === true || false;
}
function isNode(x) {
return x && x.isNode === true && x.constructor.prototype.isNode === true || false;
}
function isObjectNode(x) {
return x && x.isObjectNode === true && x.constructor.prototype.isNode === true || false;
}
function isOperatorNode(x) {
return x && x.isOperatorNode === true && x.constructor.prototype.isNode === true || false;
}
function isParenthesisNode(x) {
return x && x.isParenthesisNode === true && x.constructor.prototype.isNode === true || false;
}
function isRangeNode(x) {
return x && x.isRangeNode === true && x.constructor.prototype.isNode === true || false;
}
function isRelationalNode(x) {
return x && x.isRelationalNode === true && x.constructor.prototype.isNode === true || false;
}
function isSymbolNode(x) {
return x && x.isSymbolNode === true && x.constructor.prototype.isNode === true || false;
}
function isChain(x) {
return x && x.constructor.prototype.isChain === true || false;
}
function typeOf(x) {
var t = typeof x;
if (t === 'object') {
if (x === null) return 'null';
if (isBigNumber(x)) return 'BigNumber'; // Special: weird mashup with Decimal
if (x.constructor && x.constructor.name) return x.constructor.name;
return 'Object'; // just in case
}
return t; // can be 'string', 'number', 'boolean', 'function', 'bigint', ...
}
/**
* 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
*/
function clone$2(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$2(value);
});
}
if (x instanceof Date) return new Date(x.valueOf());
if (isBigNumber(x)) return x; // bignumbers are immutable
// object
if (isObject(x)) {
return mapObject(x, clone$2);
}
throw new TypeError("Cannot clone: unknown type of value (value: ".concat(x, ")"));
}
/**
* Apply map to all properties of an object
* @param {Object} object
* @param {function} callback
* @return {Object} Returns a copy of the object with mapped properties
*/
function mapObject(object, callback) {
var clone = {};
for (var key in object) {
if (hasOwnProperty(object, key)) {
clone[key] = callback(object[key]);
}
}
return clone;
}
/**
* Extend object a with the properties of object b
* @param {Object} a
* @param {Object} b
* @return {Object} a
*/
function extend(a, b) {
for (var prop in b) {
if (hasOwnProperty(b, prop)) {
a[prop] = b[prop];
}
}
return a;
}
/**
* Deep test equality of all fields in two pairs of arrays or objects.
* Compares values and functions strictly (ie. 2 is not the same as '2').
* @param {Array | Object} a
* @param {Array | Object} b
* @returns {boolean}
*/
function deepStrictEqual(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 (!deepStrictEqual(a[i], b[i])) {
return false;
}
}
return true;
} else if (typeof a === 'function') {
return a === b;
} else if (a instanceof Object) {
if (Array.isArray(b) || !(b instanceof Object)) {
return false;
}
for (prop in a) {
// noinspection JSUnfilteredForInLoop
if (!(prop in b) || !deepStrictEqual(a[prop], b[prop])) {
return false;
}
}
for (prop in b) {
// noinspection JSUnfilteredForInLoop
if (!(prop in a)) {
return false;
}
}
return true;
} else {
return a === b;
}
}
/**
* A safe hasOwnProperty
* @param {Object} object
* @param {string} property
*/
function hasOwnProperty(object, property) {
return object && Object.hasOwnProperty.call(object, property);
}
/**
* Shallow version of pick, creating an object composed of the picked object properties
* but not for nested properties
* @param {Object} object
* @param {string[]} properties
* @return {Object}
*/
function pickShallow(object, properties) {
var copy = {};
for (var i = 0; i < properties.length; i++) {
var key = properties[i];
var value = object[key];
if (value !== undefined) {
copy[key] = value;
}
}
return copy;
}
var MATRIX_OPTIONS = ['Matrix', 'Array']; // valid values for option matrix
var NUMBER_OPTIONS = ['number', 'BigNumber', 'Fraction']; // valid values for option number
// create a read-only version of config
var config$1 = function config(options) {
if (options) {
throw new Error('The global config is readonly. \n' + 'Please create a mathjs instance if you want to change the default configuration. \n' + 'Example:\n' + '\n' + ' import { create, all } from \'mathjs\';\n' + ' const mathjs = create(all);\n' + ' mathjs.config({ number: \'BigNumber\' });\n');
}
return Object.freeze(DEFAULT_CONFIG);
};
_extends(config$1, DEFAULT_CONFIG, {
MATRIX_OPTIONS,
NUMBER_OPTIONS
});
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
function getDefaultExportFromCjs (x) {
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
}
var typedFunction$1 = {exports: {}};
(function (module, exports) {
(function (global, factory) {
module.exports = factory() ;
})(commonjsGlobal, (function () {
function ok() {
return true;
}
function notOk() {
return false;
}
function undef() {
return undefined;
}
const NOT_TYPED_FUNCTION = 'Argument is not a typed-function.';
/**
* @typedef {{
* params: Param[],
* fn: function,
* test: function,
* implementation: function
* }} Signature
*
* @typedef {{
* types: Type[],
* hasAny: boolean,
* hasConversion: boolean,
* restParam: boolean
* }} Param
*
* @typedef {{
* name: string,
* typeIndex: number,
* test: function,
* isAny: boolean,
* conversion?: ConversionDef,
* conversionIndex: number,
* }} Type
*
* @typedef {{
* from: string,
* to: string,
* convert: function (*) : *
* }} ConversionDef
*
* @typedef {{
* name: string,
* test: function(*) : boolean,
* isAny?: boolean
* }} TypeDef
*/
/**
* @returns {() => function}
*/
function create() {
// data type tests
/**
* Returns true if the argument is a non-null "plain" object
*/
function isPlainObject(x) {
return typeof x === 'object' && x !== null && x.constructor === Object;
}
const _types = [{
name: 'number',
test: function (x) {
return typeof x === 'number';
}
}, {
name: 'string',
test: function (x) {
return typeof x === 'string';
}
}, {
name: 'boolean',
test: function (x) {
return typeof x === 'boolean';
}
}, {
name: 'Function',
test: function (x) {
return typeof x === 'function';
}
}, {
name: 'Array',
test: Array.isArray
}, {
name: 'Date',
test: function (x) {
return x instanceof Date;
}
}, {
name: 'RegExp',
test: function (x) {
return x instanceof RegExp;
}
}, {
name: 'Object',
test: isPlainObject
}, {
name: 'null',
test: function (x) {
return x === null;
}
}, {
name: 'undefined',
test: function (x) {
return x === undefined;
}
}];
const anyType = {
name: 'any',
test: ok,
isAny: true
};
// Data structures to track the types. As these are local variables in
// create(), each typed universe will get its own copy, but the variables
// will only be accessible through the (closures of the) functions supplied
// as properties of the typed object, not directly.
// These will be initialized in clear() below
let typeMap; // primary store of all types
let typeList; // Array of just type names, for the sake of ordering
// And similar data structures for the type conversions:
let nConversions = 0;
// the actual conversions are stored on a property of the destination types
// This is a temporary object, will be replaced with a function at the end
let typed = {
createCount: 0
};
/**
* Takes a type name and returns the corresponding official type object
* for that type.
*
* @param {string} typeName
* @returns {TypeDef} type
*/
function findType(typeName) {
const type = typeMap.get(typeName);
if (type) {
return type;
}
// Remainder is error handling
let message = 'Unknown type "' + typeName + '"';
const name = typeName.toLowerCase();
let otherName;
for (otherName of typeList) {
if (otherName.toLowerCase() === name) {
message += '. Did you mean "' + otherName + '" ?';
break;
}
}
throw new TypeError(message);
}
/**
* Adds an array `types` of type definitions to this typed instance.
* Each type definition should be an object with properties:
* 'name' - a string giving the name of the type; 'test' - function
* returning a boolean that tests membership in the type; and optionally
* 'isAny' - true only for the 'any' type.
*
* The second optional argument, `before`, gives the name of a type that
* these types should be added before. The new types are added in the
* order specified.
* @param {TypeDef[]} types
* @param {string | boolean} [beforeSpec='any'] before
*/
function addTypes(types) {
let beforeSpec = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'any';
const beforeIndex = beforeSpec ? findType(beforeSpec).index : typeList.length;
const newTypes = [];
for (let i = 0; i < types.length; ++i) {
if (!types[i] || typeof types[i].name !== 'string' || typeof types[i].test !== 'function') {
throw new TypeError('Object with properties {name: string, test: function} expected');
}
const typeName = types[i].name;
if (typeMap.has(typeName)) {
throw new TypeError('Duplicate type name "' + typeName + '"');
}
newTypes.push(typeName);
typeMap.set(typeName, {
name: typeName,
test: types[i].test,
isAny: types[i].isAny,
index: beforeIndex + i,
conversionsTo: [] // Newly added type can't have any conversions to it
});
}
// update the typeList
const affectedTypes = typeList.slice(beforeIndex);
typeList = typeList.slice(0, beforeIndex).concat(newTypes).concat(affectedTypes);
// Fix the indices
for (let i = beforeIndex + newTypes.length; i < typeList.length; ++i) {
typeMap.get(typeList[i]).index = i;
}
}
/**
* Removes all types and conversions from this typed instance.
* May cause previously constructed typed-functions to throw
* strange errors when they are called with types that do not
* match any of their signatures.
*/
function clear() {
typeMap = new Map();
typeList = [];
nConversions = 0;
addTypes([anyType], false);
}
// initialize the types to the default list
clear();
addTypes(_types);
/**
* Removes all conversions, leaving the types alone.
*/
function clearConversions() {
let typeName;
for (typeName of typeList) {
typeMap.get(typeName).conversionsTo = [];
}
nConversions = 0;
}
/**
* Find the type names that match a value.
* @param {*} value
* @return {string[]} Array of names of types for which
* the type test matches the value.
*/
function findTypeNames(value) {
const matches = typeList.filter(name => {
const type = typeMap.get(name);
return !type.isAny && type.test(value);
});
if (matches.length) {
return matches;
}
return ['any'];
}
/**
* Check if an entity is a typed function created by any instance
* @param {any} entity
* @returns {boolean}
*/
function isTypedFunction(entity) {
return entity && typeof entity === 'function' && '_typedFunctionData' in entity;
}
/**
* Find a specific signature from a (composed) typed function, for example:
*
* typed.findSignature(fn, ['number', 'string'])
* typed.findSignature(fn, 'number, string')
* typed.findSignature(fn, 'number,string', {exact: true})
*
* This function findSignature will by default return the best match to
* the given signature, possibly employing type conversions.
*
* The (optional) third argument is a plain object giving options
* controlling the signature search. Currently the only implemented
* option is `exact`: if specified as true (default is false), only
* exact matches will be returned (i.e. signatures for which `fn` was
* directly defined). Note that a (possibly different) type matching
* `any`, or one or more instances of TYPE matching `...TYPE` are
* considered exact matches in this regard, as no conversions are used.
*
* This function returns a "signature" object, as does `typed.resolve()`,
* which is a plain object with four keys: `params` (the array of parameters
* for this signature), `fn` (the originally supplied function for this
* signature), `test` (a generated function that determines if an argument
* list matches this signature, and `implementation` (the function to call
* on a matching argument list, that performs conversions if necessary and
* then calls the originally supplied function).
*
* @param {Function} fn A typed-function
* @param {string | string[]} signature
* Signature to be found, can be an array or a comma separated string.
* @param {object} options Controls the signature search as documented
* @return {{ params: Param[], fn: function, test: function, implementation: function }}
* Returns the matching signature, or throws an error when no signature
* is found.
*/
function findSignature(fn, signature, options) {
if (!isTypedFunction(fn)) {
throw new TypeError(NOT_TYPED_FUNCTION);
}
// Canonicalize input
const exact = options && options.exact;
const stringSignature = Array.isArray(signature) ? signature.join(',') : signature;
const params = parseSignature(stringSignature);
const canonicalSignature = stringifyParams(params);
// First hope we get lucky and exactly match a signature
if (!exact || canonicalSignature in fn.signatures) {
// OK, we can check the internal signatures
const match = fn._typedFunctionData.signatureMap.get(canonicalSignature);
if (match) {
return match;
}
}
// Oh well, we did not; so we have to go back and check the parameters
// one by one, in order to catch things like `any` and rest params.
// Note here we can assume there is at least one parameter, because
// the empty signature would have matched successfully above.
const nParams = params.length;
let remainingSignatures;
if (exact) {
remainingSignatures = [];
let name;
for (name in fn.signatures) {
remainingSignatures.push(fn._typedFunctionData.signatureMap.get(name));
}
} else {
remainingSignatures = fn._typedFunctionData.signatures;
}
for (let i = 0; i < nParams; ++i) {
const want = params[i];
const filteredSignatures = [];
let possibility;
for (possibility of remainingSignatures) {
const have = getParamAtIndex(possibility.params, i);
if (!have || want.restParam && !have.restParam) {
continue;
}
if (!have.hasAny) {
// have to check all of the wanted types are available
const haveTypes = paramTypeSet(have);
if (want.types.some(wtype => !haveTypes.has(wtype.name))) {
continue;
}
}
// OK, this looks good
filteredSignatures.push(possibility);
}
remainingSignatures = filteredSignatures;
if (remainingSignatures.length === 0) break;
}
// Return the first remaining signature that was totally matched:
let candidate;
for (candidate of remainingSignatures) {
if (candidate.params.length <= nParams) {
return candidate;
}
}
throw new TypeError('Signature not found (signature: ' + (fn.name || 'unnamed') + '(' + stringifyParams(params, ', ') + '))');
}
/**
* Find the proper function to call for a specific signature from
* a (composed) typed function, for example:
*
* typed.find(fn, ['number', 'string'])
* typed.find(fn, 'number, string')
* typed.find(fn, 'number,string', {exact: true})
*
* This function find will by default return the best match to
* the given signature, possibly employing type conversions (and returning
* a function that will perform those conversions as needed). The
* (optional) third argument is a plain object giving options contolling
* the signature search. Currently only the option `exact` is implemented,
* which defaults to "false". If `exact` is specified as true, then only
* exact matches will be returned (i.e. signatures for which `fn` was
* directly defined). Uses of `any` and `...TYPE` are considered exact if
* no conversions are necessary to apply the corresponding function.
*
* @param {Function} fn A typed-function
* @param {string | string[]} signature
* Signature to be found, can be an array or a comma separated string.
* @param {object} options Controls the signature match as documented
* @return {function}
* Returns the function to call for the given signature, or throws an
* error if no match is found.
*/
function find(fn, signature, options) {
return findSignature(fn, signature, options).implementation;
}
/**
* Convert a given value to another data type, specified by type name.
*
* @param {*} value
* @param {string} typeName
*/
function convert(value, typeName) {
// check conversion is needed
const type = findType(typeName);
if (type.test(value)) {
return value;
}
const conversions = type.conversionsTo;
if (conversions.length === 0) {
throw new Error('There are no conversions to ' + typeName + ' defined.');
}
for (let i = 0; i < conversions.length; i++) {
const fromType = findType(conversions[i].from);
if (fromType.test(value)) {
return conversions[i].convert(value);
}
}
throw new Error('Cannot convert ' + value + ' to ' + typeName);
}
/**
* Stringify parameters in a normalized way
* @param {Param[]} params
* @param {string} [','] separator
* @return {string}
*/
function stringifyParams(params) {
let separator = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ',';
return params.map(p => p.name).join(separator);
}
/**
* Parse a parameter, like "...number | boolean"
* @param {string} param
* @return {Param} param
*/
function parseParam(param) {
const restParam = param.indexOf('...') === 0;
const types = !restParam ? param : param.length > 3 ? param.slice(3) : 'any';
const typeDefs = types.split('|').map(s => findType(s.trim()));
let hasAny = false;
let paramName = restParam ? '...' : '';
const exactTypes = typeDefs.map(function (type) {
hasAny = type.isAny || hasAny;
paramName += type.name + '|';
return {
name: type.name,
typeIndex: type.index,
test: type.test,
isAny: type.isAny,
conversion: null,
conversionIndex: -1
};
});
return {
types: exactTypes,
name: paramName.slice(0, -1),
// remove trailing '|' from above
hasAny,
hasConversion: false,
restParam
};
}
/**
* Expands a parsed parameter with the types available from currently
* defined conversions.
* @param {Param} param
* @return {Param} param
*/
function expandParam(param) {
const typeNames = param.types.map(t => t.name);
const matchingConversions = availableConversions(typeNames);
let hasAny = param.hasAny;
let newName = param.name;
const convertibleTypes = matchingConversions.map(function (conversion) {
const type = findType(conversion.from);
hasAny = type.isAny || hasAny;
newName += '|' + conversion.from;
return {
name: conversion.from,
typeIndex: type.index,
test: type.test,
isAny: type.isAny,
conversion,
conversionIndex: conversion.index
};
});
return {
types: param.types.concat(convertibleTypes),
name: newName,
hasAny,
hasConversion: convertibleTypes.length > 0,
restParam: param.restParam
};
}
/**
* Return the set of type names in a parameter.
* Caches the result for efficiency
*
* @param {Param} param
* @return {Set<string>} typenames
*/
function paramTypeSet(param) {
if (!param.typeSet) {
param.typeSet = new Set();
param.types.forEach(type => param.typeSet.add(type.name));
}
return param.typeSet;
}
/**
* Parse a signature with comma separated parameters,
* like "number | boolean, ...string"
*
* @param {string} signature
* @return {Param[]} params
*/
function parseSignature(rawSignature) {
const params = [];
if (typeof rawSignature !== 'string') {
throw new TypeError('Signatures must be strings');
}
const signature = rawSignature.trim();
if (signature === '') {
return params;
}
const rawParams = signature.split(',');
for (let i = 0; i < rawParams.length; ++i) {
const parsedParam = parseParam(rawParams[i].trim());
if (parsedParam.restParam && i !== rawParams.length - 1) {
throw new SyntaxError('Unexpected rest parameter "' + rawParams[i] + '": ' + 'only allowed for the last parameter');
}
// if invalid, short-circuit (all the types may have been filtered)
if (parsedParam.types.length === 0) {
return null;
}
params.push(parsedParam);
}
return params;
}
/**
* Test whether a set of params contains a restParam
* @param {Param[]} params
* @return {boolean} Returns true when the last parameter is a restParam
*/
function hasRestParam(params) {
const param = last(params);
return param ? param.restParam : false;
}
/**
* Create a type test for a single parameter, which can have one or multiple
* types.
* @param {Param} param
* @return {function(x: *) : boolean} Returns a test function
*/
function compileTest(param) {
if (!param || param.types.length === 0) {
// nothing to do
return ok;
} else if (param.types.length === 1) {
return findType(param.types[0].name).test;
} else if (param.types.length === 2) {
const test0 = findType(param.types[0].name).test;
const test1 = findType(param.types[1].name).test;
return function or(x) {
return test0(x) || test1(x);
};
} else {
// param.types.length > 2
const tests = param.types.map(function (type) {
return findType(type.name).test;
});
return function or(x) {
for (let i = 0; i < tests.length; i++) {
if (tests[i](x)) {
return true;
}
}
return false;
};
}
}
/**
* Create a test for all parameters of a signature
* @param {Param[]} params
* @return {function(args: Array<*>) : boolean}
*/
function compileTests(params) {
let tests, test0, test1;
if (hasRestParam(params)) {
// variable arguments like '...number'
tests = initial(params).map(compileTest);
const varIndex = tests.length;
const lastTest = compileTest(last(params));
const testRestParam = function (args) {
for (let i = varIndex; i < args.length; i++) {
if (!lastTest(args[i])) {
return false;
}
}
return true;
};
return function testArgs(args) {
for (let i = 0; i < tests.length; i++) {
if (!tests[i](args[i])) {
return false;
}
}
return testRestParam(args) && args.length >= varIndex + 1;
};
} else {
// no variable arguments
if (params.length === 0) {
return function testArgs(args) {
return args.length === 0;
};
} else if (params.length === 1) {
test0 = compileTest(params[0]);
return function testArgs(args) {
return test0(args[0]) && args.length === 1;
};
} else if (params.length === 2) {
test0 = compileTest(params[0]);
test1 = compileTest(params[1]);
return function testArgs(args) {
return test0(args[0]) && test1(args[1]) && args.length === 2;
};
} else {
// arguments.length > 2
tests = params.map(compileTest);
return function testArgs(args) {
for (let i = 0; i < tests.length; i++) {
if (!tests[i](args[i])) {
return false;
}
}
return args.length === tests.length;
};
}
}
}
/**
* Find the parameter at a specific index of a Params list.
* Handles rest parameters.
* @param {Param[]} params
* @param {number} index
* @return {Param | null} Returns the matching parameter when found,
* null otherwise.
*/
function getParamAtIndex(params, index) {
return index < params.length ? params[index] : hasRestParam(params) ? last(params) : null;
}
/**
* Get all type names of a parameter
* @param {Params[]} params
* @param {number} index
* @return {string[]} Returns an array with type names
*/
function getTypeSetAtIndex(params, index) {
const param = getParamAtIndex(params, index);
if (!param) {
return new Set();
}
return paramTypeSet(param);
}
/**
* Test whether a type is an exact type or conversion
* @param {Type} type
* @return {boolean} Returns true when
*/
function isExactType(type) {
return type.conversion === null || type.conversion === undefined;
}
/**
* Helper function for creating error messages: create an array with
* all available types on a specific argument index.
* @param {Signature[]} signatures
* @param {number} index
* @return {string[]} Returns an array with available types
*/
function mergeExpectedParams(signatures, index) {
const typeSet = new Set();
signatures.forEach(signature => {
const paramSet = getTypeSetAtIndex(signature.params, index);
let name;
for (name of paramSet) {
typeSet.add(name);
}
});
return typeSet.has('any') ? ['any'] : Array.from(typeSet);
}
/**
* Create
* @param {string} name The name of the function
* @param {array.<*>} args The actual arguments passed to the function
* @param {Signature[]} signatures A list with available signatures
* @return {TypeError} Returns a type error with additional data
* attached to it in the property `data`
*/
function createError(name, args, signatures) {
let err, expected;
const _name = name || 'unnamed';
// test for wrong type at some index
let matchingSignatures = signatures;
let index;
for (index = 0; index < args.length; index++) {
const nextMatchingDefs = [];
matchingSignatures.forEach(signature => {
const param = getParamAtIndex(signature.params, index);
const test = compileTest(param);
if ((index < signature.params.length || hasRestParam(signature.params)) && test(args[index])) {
nextMatchingDefs.push(signature);
}
});
if (nextMatchingDefs.length === 0) {
// no matching signatures anymore, throw error "wrong type"
expected = mergeExpectedParams(matchingSignatures, index);
if (expected.length > 0) {
const actualTypes = findTypeNames(args[index]);
err = new TypeError('Unexpected type of argument in function ' + _name + ' (expected: ' + expected.join(' or ') + ', actual: ' + actualTypes.join(' | ') + ', index: ' + index + ')');
err.data = {
category: 'wrongType',
fn: _name,
index,
actual: actualTypes,
expected
};
return err;
}
} else {
matchingSignatures = nextMatchingDefs;
}
}
// test for too few arguments
const lengths = matchingSignatures.map(function (signature) {
return hasRestParam(signature.params) ? Infinity : signature.params.length;
});
if (args.length < Math.min.apply(null, lengths)) {
expected = mergeExpectedParams(matchingSignatures, index);
err = new TypeError('Too few arguments in function ' + _name + ' (expected: ' + expected.join(' or ') + ', index: ' + args.length + ')');
err.data = {
category: 'tooFewArgs',
fn: _name,
index: args.length,
expected
};
return err;
}
// test for too many arguments
const maxLength = Math.max.apply(null, lengths);
if (args.length > maxLength) {
err = new TypeError('Too many arguments in function ' + _name + ' (expected: ' + maxLength + ', actual: ' + args.length + ')');
err.data = {
category: 'tooManyArgs',
fn: _name,
index: args.length,
expectedLength: maxLength
};
return err;
}
// Generic error
const argTypes = [];
for (let i = 0; i < args.length; ++i) {
argTypes.push(findTypeNames(args[i]).join('|'));
}
err = new TypeError('Arguments of type "' + argTypes.join(', ') + '" do not match any of the defined signatures of function ' + _name + '.');
err.data = {
category: 'mismatch',
actual: argTypes
};
return err;
}
/**
* Find the lowest index of all exact types of a parameter (no conversions)
* @param {Param} param
* @return {number} Returns the index of the lowest type in typed.types
*/
function getLowestTypeIndex(param) {
let min = typeList.length + 1;
for (let i = 0; i < param.types.length; i++) {
if (isExactType(param.types[i])) {
min = Math.min(min, param.types[i].typeIndex);
}
}
return min;
}
/**
* Find the lowest index of the conversion of all types of the parameter
* having a conversion
* @param {Param} param
* @return {number} Returns the lowest index of the conversions of this type
*/
function getLowestConversionIndex(param) {
let min = nConversions + 1;
for (let i = 0; i < param.types.length; i++) {
if (!isExactType(param.types[i])) {
min = Math.min(min, param.types[i].conversionIndex);
}
}
return min;
}
/**
* Compare two params
* @param {Param} param1
* @param {Param} param2
* @return {number} returns -1 when param1 must get a lower
* index than param2, 1 when the opposite,
* or zero when both are equal
*/
function compareParams(param1, param2) {
// We compare a number of metrics on a param in turn:
// 1) 'any' parameters are the least preferred
if (param1.hasAny) {
if (!param2.hasAny) {
return 1;
}
} else if (param2.hasAny) {
return -1;
}
// 2) Prefer non-rest to rest parameters
if (param1.restParam) {
if (!param2.restParam) {
return 1;
}
} else if (param2.restParam) {
return -1;
}
// 3) Prefer exact type match to conversions
if (param1.hasConversion) {
if (!param2.hasConversion) {
return 1;
}
} else if (param2.hasConversion) {
return -1;
}
// 4) Prefer lower type index:
const typeDiff = getLowestTypeIndex(param1) - getLowestTypeIndex(param2);
if (typeDiff < 0) {
return -1;
}
if (typeDiff > 0) {
return 1;
}
// 5) Prefer lower conversion index
const convDiff = getLowestConversionIndex(param1) - getLowestConversionIndex(param2);
if (convDiff < 0) {
return -1;
}
if (convDiff > 0) {
return 1;
}
// Don't have a basis for preference
return 0;
}
/**
* Compare two signatures
* @param {Signature} signature1
* @param {Signature} signature2
* @return {number} returns a negative number when param1 must get a lower
* index than param2, a positive number when the opposite,
* or zero when both are equal
*/
function compareSignatures(signature1, signature2) {
const pars1 = signature1.params;
const pars2 = signature2.params;
const last1 = last(pars1);
const last2 = last(pars2);
const hasRest1 = hasRestParam(pars1);
const hasRest2 = hasRestParam(pars2);
// We compare a number of metrics on signatures in turn:
// 1) An "any rest param" is least preferred
if (hasRest1 && last1.hasAny) {
if (!hasRest2 || !last2.hasAny) {
return 1;
}
} else if (hasRest2 && last2.hasAny) {
return -1;
}
// 2) Minimize the number of 'any' parameters
let any1 = 0;
let conv1 = 0;
let par;
for (par of pars1) {
if (par.hasAny) ++any1;
if (par.hasConversion) ++conv1;
}
let any2 = 0;
let conv2 = 0;
for (par of pars2) {
if (par.hasAny) ++any2;
if (par.hasConversion) ++conv2;
}
if (any1 !== any2) {
return any1 - any2;
}
// 3) A conversion rest param is less preferred
if (hasRest1 && last1.hasConversion) {
if (!hasRest2 || !last2.hasConversion) {
return 1;
}
} else if (hasRest2 && last2.hasConversion) {
return -1;
}
// 4) Minimize the number of conversions
if (conv1 !== conv2) {
return conv1 - conv2;
}
// 5) Prefer no rest param
if (hasRest1) {
if (!hasRest2) {
return 1;
}
} else if (hasRest2) {
return -1;
}
// 6) Prefer shorter with rest param, longer without
const lengthCriterion = (pars1.length - pars2.length) * (hasRest1 ? -1 : 1);
if (lengthCriterion !== 0) {
return lengthCriterion;
}
// Signatures are identical in each of the above metrics.
// In particular, they are the same length.
// We can therefore compare the parameters one by one.
// First we count which signature has more preferred parameters.
const comparisons = [];
let tc = 0;
for (let i = 0; i < pars1.length; ++i) {
const thisComparison = compareParams(pars1[i], pars2[i]);
comparisons.push(thisComparison);
tc += thisComparison;
}
if (tc !== 0) {
return tc;
}
// They have the same number of preferred parameters, so go by the
// earliest parameter in which we have a preference.
// In other words, dispatch is driven somewhat more by earlier
// parameters than later ones.
let c;
for (c of comparisons) {
if (c !== 0) {
return c;
}
}
// It's a tossup:
return 0;
}
/**
* Produce a list of all conversions from distinct types to one of
* the given types.
*
* @param {string[]} typeNames
* @return {ConversionDef[]} Returns the conversions that are available
* resulting in any given type (if any)
*/
function availableConversions(typeNames) {
if (typeNames.length === 0) {
return [];
}
const types = typeNames.map(findType);
if (typeNames.length > 1) {
types.sort((t1, t2) => t1.index - t2.index);
}
let matches = types[0].conversionsTo;
if (typeNames.length === 1) {
return matches;
}
matches = matches.concat([]); // shallow copy the matches
// Since the types are now in index order, we just want the first
// occurrence of any from type:
const knownTypes = new Set(typeNames);
for (let i = 1; i < types.length; ++i) {
let newMatch;
for (newMatch of types[i].conversionsTo) {
if (!knownTypes.has(newMatch.from)) {
matches.push(newMatch);
knownTypes.add(newMatch.from);
}
}
}
return matches;
}
/**
* Preprocess arguments before calling the original function:
* - if needed convert the parameters
* - in case of rest parameters, move the rest parameters into an Array
* @param {Param[]} params
* @param {function} fn
* @return {function} Returns a wrapped function
*/
function compileArgsPreprocessing(params, fn) {
let fnConvert = fn;
// TODO: can we make this wrapper function smarter/simpler?
if (params.some(p => p.hasConversion)) {
const restParam = hasRestParam(params);
const compiledConversions = params.map(compileArgConversion);
fnConvert = function convertArgs() {
const args = [];
const last = restParam ? arguments.length - 1 : arguments.length;
for (let i = 0; i < last; i++) {
args[i] = compiledConversions[i](arguments[i]);
}
if (restParam) {
args[last] = arguments[last].map(compiledConversions[last]);
}
return fn.apply(this, args);
};
}
let fnPreprocess = fnConvert;
if (hasRestParam(params)) {
const offset = params.length - 1;
fnPreprocess = function preprocessRestParams() {
return fnConvert.apply(this, slice(arguments, 0, offset).concat([slice(arguments, offset)]));
};
}
return fnPreprocess;
}
/**
* Compile conversion for a parameter to the right type
* @param {Param} param
* @return {function} Returns the wrapped function that will convert arguments
*
*/
function compileArgConversion(param) {
let test0, test1, conversion0, conversion1;
const tests = [];
const conversions = [];
param.types.forEach(function (type) {
if (type.conversion) {
tests.push(findType(type.conversion.from).test);
conversions.push(type.conversion.convert);
}
});
// create optimized conversion functions depending on the number of conversions
switch (conversions.length) {
case 0:
return function convertArg(arg) {
return arg;
};
case 1:
test0 = tests[0];
conversion0 = conversions[0];
return function