UNPKG

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
(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