UNPKG

9.72 kBJavaScriptView Raw
1/**
2 * Create a typed-function which checks the types of the arguments and
3 * can match them against multiple provided signatures. The typed-function
4 * automatically converts inputs in order to find a matching signature.
5 * Typed functions throw informative errors in case of wrong input arguments.
6 *
7 * See the library [typed-function](https://github.com/josdejong/typed-function)
8 * for detailed documentation.
9 *
10 * Syntax:
11 *
12 * math.typed(name, signatures) : function
13 * math.typed(signatures) : function
14 *
15 * Examples:
16 *
17 * // create a typed function with multiple types per argument (type union)
18 * const fn2 = typed({
19 * 'number | boolean': function (b) {
20 * return 'b is a number or boolean'
21 * },
22 * 'string, number | boolean': function (a, b) {
23 * return 'a is a string, b is a number or boolean'
24 * }
25 * })
26 *
27 * // create a typed function with an any type argument
28 * const log = typed({
29 * 'string, any': function (event, data) {
30 * console.log('event: ' + event + ', data: ' + JSON.stringify(data))
31 * }
32 * })
33 *
34 * @param {string} [name] Optional name for the typed-function
35 * @param {Object<string, function>} signatures Object with one or multiple function signatures
36 * @returns {function} The created typed-function.
37 */
38
39import {
40 isAccessorNode,
41 isArray,
42 isArrayNode,
43 isAssignmentNode,
44 isBigNumber,
45 isBlockNode,
46 isBoolean,
47 isChain,
48 isComplex,
49 isConditionalNode,
50 isConstantNode,
51 isDate,
52 isDenseMatrix,
53 isFraction,
54 isFunction,
55 isFunctionAssignmentNode,
56 isFunctionNode,
57 isHelp,
58 isIndex,
59 isIndexNode,
60 isMatrix,
61 isNode,
62 isNull,
63 isNumber,
64 isObject,
65 isObjectNode,
66 isOperatorNode,
67 isParenthesisNode,
68 isRange,
69 isRangeNode,
70 isRegExp,
71 isResultSet,
72 isSparseMatrix,
73 isString,
74 isSymbolNode,
75 isUndefined,
76 isUnit
77} from '../../utils/is'
78import typedFunction from 'typed-function'
79import { digits } from '../../utils/number'
80import { factory } from '../../utils/factory'
81
82// returns a new instance of typed-function
83let _createTyped = function () {
84 // initially, return the original instance of typed-function
85 // consecutively, return a new instance from typed.create.
86 _createTyped = typedFunction.create
87 return typedFunction
88}
89
90const dependencies = [
91 '?BigNumber',
92 '?Complex',
93 '?DenseMatrix',
94 '?Fraction'
95]
96
97/**
98 * Factory function for creating a new typed instance
99 * @param {Object} dependencies Object with data types like Complex and BigNumber
100 * @returns {Function}
101 */
102export const createTyped = /* #__PURE__ */ factory('typed', dependencies, function createTyped ({ BigNumber, Complex, DenseMatrix, Fraction }) {
103 // TODO: typed-function must be able to silently ignore signatures with unknown data types
104
105 // get a new instance of typed-function
106 const typed = _createTyped()
107
108 // define all types. The order of the types determines in which order function
109 // arguments are type-checked (so for performance it's important to put the
110 // most used types first).
111 typed.types = [
112 { name: 'number', test: isNumber },
113 { name: 'Complex', test: isComplex },
114 { name: 'BigNumber', test: isBigNumber },
115 { name: 'Fraction', test: isFraction },
116 { name: 'Unit', test: isUnit },
117 { name: 'string', test: isString },
118 { name: 'Chain', test: isChain },
119 { name: 'Array', test: isArray },
120 { name: 'Matrix', test: isMatrix },
121 { name: 'DenseMatrix', test: isDenseMatrix },
122 { name: 'SparseMatrix', test: isSparseMatrix },
123 { name: 'Range', test: isRange },
124 { name: 'Index', test: isIndex },
125 { name: 'boolean', test: isBoolean },
126 { name: 'ResultSet', test: isResultSet },
127 { name: 'Help', test: isHelp },
128 { name: 'function', test: isFunction },
129 { name: 'Date', test: isDate },
130 { name: 'RegExp', test: isRegExp },
131 { name: 'null', test: isNull },
132 { name: 'undefined', test: isUndefined },
133
134 { name: 'AccessorNode', test: isAccessorNode },
135 { name: 'ArrayNode', test: isArrayNode },
136 { name: 'AssignmentNode', test: isAssignmentNode },
137 { name: 'BlockNode', test: isBlockNode },
138 { name: 'ConditionalNode', test: isConditionalNode },
139 { name: 'ConstantNode', test: isConstantNode },
140 { name: 'FunctionNode', test: isFunctionNode },
141 { name: 'FunctionAssignmentNode', test: isFunctionAssignmentNode },
142 { name: 'IndexNode', test: isIndexNode },
143 { name: 'Node', test: isNode },
144 { name: 'ObjectNode', test: isObjectNode },
145 { name: 'OperatorNode', test: isOperatorNode },
146 { name: 'ParenthesisNode', test: isParenthesisNode },
147 { name: 'RangeNode', test: isRangeNode },
148 { name: 'SymbolNode', test: isSymbolNode },
149
150 { name: 'Object', test: isObject } // order 'Object' last, it matches on other classes too
151 ]
152
153 typed.conversions = [
154 {
155 from: 'number',
156 to: 'BigNumber',
157 convert: function (x) {
158 if (!BigNumber) {
159 throwNoBignumber(x)
160 }
161
162 // note: conversion from number to BigNumber can fail if x has >15 digits
163 if (digits(x) > 15) {
164 throw new TypeError('Cannot implicitly convert a number with >15 significant digits to BigNumber ' +
165 '(value: ' + x + '). ' +
166 'Use function bignumber(x) to convert to BigNumber.')
167 }
168 return new BigNumber(x)
169 }
170 }, {
171 from: 'number',
172 to: 'Complex',
173 convert: function (x) {
174 if (!Complex) {
175 throwNoComplex(x)
176 }
177
178 return new Complex(x, 0)
179 }
180 }, {
181 from: 'number',
182 to: 'string',
183 convert: function (x) {
184 return x + ''
185 }
186 }, {
187 from: 'BigNumber',
188 to: 'Complex',
189 convert: function (x) {
190 if (!Complex) {
191 throwNoComplex(x)
192 }
193
194 return new Complex(x.toNumber(), 0)
195 }
196 }, {
197 from: 'Fraction',
198 to: 'BigNumber',
199 convert: function (x) {
200 throw new TypeError('Cannot implicitly convert a Fraction to BigNumber or vice versa. ' +
201 'Use function bignumber(x) to convert to BigNumber or fraction(x) to convert to Fraction.')
202 }
203 }, {
204 from: 'Fraction',
205 to: 'Complex',
206 convert: function (x) {
207 if (!Complex) {
208 throwNoComplex(x)
209 }
210
211 return new Complex(x.valueOf(), 0)
212 }
213 }, {
214 from: 'number',
215 to: 'Fraction',
216 convert: function (x) {
217 if (!Fraction) {
218 throwNoFraction(x)
219 }
220
221 const f = new Fraction(x)
222 if (f.valueOf() !== x) {
223 throw new TypeError('Cannot implicitly convert a number to a Fraction when there will be a loss of precision ' +
224 '(value: ' + x + '). ' +
225 'Use function fraction(x) to convert to Fraction.')
226 }
227 return f
228 }
229 }, {
230 // FIXME: add conversion from Fraction to number, for example for `sqrt(fraction(1,3))`
231 // from: 'Fraction',
232 // to: 'number',
233 // convert: function (x) {
234 // return x.valueOf()
235 // }
236 // }, {
237 from: 'string',
238 to: 'number',
239 convert: function (x) {
240 const n = Number(x)
241 if (isNaN(n)) {
242 throw new Error('Cannot convert "' + x + '" to a number')
243 }
244 return n
245 }
246 }, {
247 from: 'string',
248 to: 'BigNumber',
249 convert: function (x) {
250 if (!BigNumber) {
251 throwNoBignumber(x)
252 }
253
254 try {
255 return new BigNumber(x)
256 } catch (err) {
257 throw new Error('Cannot convert "' + x + '" to BigNumber')
258 }
259 }
260 }, {
261 from: 'string',
262 to: 'Fraction',
263 convert: function (x) {
264 if (!Fraction) {
265 throwNoFraction(x)
266 }
267
268 try {
269 return new Fraction(x)
270 } catch (err) {
271 throw new Error('Cannot convert "' + x + '" to Fraction')
272 }
273 }
274 }, {
275 from: 'string',
276 to: 'Complex',
277 convert: function (x) {
278 if (!Complex) {
279 throwNoComplex(x)
280 }
281
282 try {
283 return new Complex(x)
284 } catch (err) {
285 throw new Error('Cannot convert "' + x + '" to Complex')
286 }
287 }
288 }, {
289 from: 'boolean',
290 to: 'number',
291 convert: function (x) {
292 return +x
293 }
294 }, {
295 from: 'boolean',
296 to: 'BigNumber',
297 convert: function (x) {
298 if (!BigNumber) {
299 throwNoBignumber(x)
300 }
301
302 return new BigNumber(+x)
303 }
304 }, {
305 from: 'boolean',
306 to: 'Fraction',
307 convert: function (x) {
308 if (!Fraction) {
309 throwNoFraction(x)
310 }
311
312 return new Fraction(+x)
313 }
314 }, {
315 from: 'boolean',
316 to: 'string',
317 convert: function (x) {
318 return String(x)
319 }
320 }, {
321 from: 'Array',
322 to: 'Matrix',
323 convert: function (array) {
324 if (!DenseMatrix) {
325 throwNoMatrix()
326 }
327
328 return new DenseMatrix(array)
329 }
330 }, {
331 from: 'Matrix',
332 to: 'Array',
333 convert: function (matrix) {
334 return matrix.valueOf()
335 }
336 }
337 ]
338
339 return typed
340})
341
342function throwNoBignumber (x) {
343 throw new Error(`Cannot convert value ${x} into a BigNumber: no class 'BigNumber' provided`)
344}
345
346function throwNoComplex (x) {
347 throw new Error(`Cannot convert value ${x} into a Complex number: no class 'Complex' provided`)
348}
349
350function throwNoMatrix () {
351 throw new Error('Cannot convert array into a Matrix: no class \'DenseMatrix\' provided')
352}
353
354function throwNoFraction (x) {
355 throw new Error(`Cannot convert value ${x} into a Fraction, no class 'Fraction' provided.`)
356}