forked from josdejong/mathjs
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathimport.js
More file actions
310 lines (279 loc) · 9.28 KB
/
Copy pathimport.js
File metadata and controls
310 lines (279 loc) · 9.28 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
'use strict'
const lazy = require('../../utils/object').lazy
const isFactory = require('../../utils/object').isFactory
const traverse = require('../../utils/object').traverse
const ArgumentsError = require('../../error/ArgumentsError')
function factory (type, config, load, typed, math) {
/**
* Import functions from an object or a module
*
* Syntax:
*
* math.import(object)
* math.import(object, options)
*
* Where:
*
* - `object: Object`
* An object with functions to be imported.
* - `options: Object` An object with import options. Available options:
* - `override: boolean`
* If true, existing functions will be overwritten. False by default.
* - `silent: boolean`
* If true, the function will not throw errors on duplicates or invalid
* types. False by default.
* - `wrap: boolean`
* If true, the functions will be wrapped in a wrapper function
* which converts data types like Matrix to primitive data types like Array.
* The wrapper is needed when extending math.js with libraries which do not
* support these data type. False by default.
*
* Examples:
*
* // define new functions and variables
* math.import({
* myvalue: 42,
* hello: function (name) {
* return 'hello, ' + name + '!'
* }
* })
*
* // use the imported function and variable
* math.myvalue * 2 // 84
* math.hello('user') // 'hello, user!'
*
* // import the npm module 'numbers'
* // (must be installed first with `npm install numbers`)
* math.import(require('numbers'), {wrap: true})
*
* math.fibonacci(7) // returns 13
*
* @param {Object | Array} object Object with functions to be imported.
* @param {Object} [options] Import options.
*/
function mathImport (object, options) {
const num = arguments.length
if (num !== 1 && num !== 2) {
throw new ArgumentsError('import', num, 1, 2)
}
if (!options) {
options = {}
}
// TODO: allow a typed-function with name too
if (isFactory(object)) {
_importFactory(object, options)
} else if (Array.isArray(object)) {
object.forEach(function (entry) {
mathImport(entry, options)
})
} else if (typeof object === 'object') {
// a map with functions
for (const name in object) {
if (object.hasOwnProperty(name)) {
const value = object[name]
if (isSupportedType(value)) {
_import(name, value, options)
} else if (isFactory(object)) {
_importFactory(object, options)
} else {
mathImport(value, options)
}
}
}
} else {
if (!options.silent) {
throw new TypeError('Factory, Object, or Array expected')
}
}
}
/**
* Add a property to the math namespace and create a chain proxy for it.
* @param {string} name
* @param {*} value
* @param {Object} options See import for a description of the options
* @private
*/
function _import (name, value, options) {
// TODO: refactor this function, it's to complicated and contains duplicate code
if (options.wrap && typeof value === 'function') {
// create a wrapper around the function
value = _wrap(value)
}
if (isTypedFunction(math[name]) && isTypedFunction(value)) {
if (options.override) {
// give the typed function the right name
value = typed(name, value.signatures)
} else {
// merge the existing and typed function
value = typed(math[name], value)
}
math[name] = value
_importTransform(name, value)
math.emit('import', name, function resolver () {
return value
})
return
}
if (math[name] === undefined || options.override) {
math[name] = value
_importTransform(name, value)
math.emit('import', name, function resolver () {
return value
})
return
}
if (!options.silent) {
throw new Error('Cannot import "' + name + '": already exists')
}
}
function _importTransform (name, value) {
if (value && typeof value.transform === 'function') {
math.expression.transform[name] = value.transform
if (allowedInExpressions(name)) {
math.expression.mathWithTransform[name] = value.transform
}
} else {
// remove existing transform
delete math.expression.transform[name]
if (allowedInExpressions(name)) {
math.expression.mathWithTransform[name] = value
}
}
}
function _deleteTransform (name) {
delete math.expression.transform[name]
if (allowedInExpressions(name)) {
math.expression.mathWithTransform[name] = math[name]
} else {
delete math.expression.mathWithTransform[name]
}
}
/**
* Create a wrapper a round an function which converts the arguments
* to their primitive values (like convert a Matrix to Array)
* @param {Function} fn
* @return {Function} Returns the wrapped function
* @private
*/
function _wrap (fn) {
const wrapper = function wrapper () {
const args = []
for (let i = 0, len = arguments.length; i < len; i++) {
const arg = arguments[i]
args[i] = arg && arg.valueOf()
}
return fn.apply(math, args)
}
if (fn.transform) {
wrapper.transform = fn.transform
}
return wrapper
}
/**
* Import an instance of a factory into math.js
* @param {{factory: Function, name: string, path: string, math: boolean}} factory
* @param {Object} options See import for a description of the options
* @private
*/
function _importFactory (factory, options) {
if (typeof factory.name === 'string') {
const name = factory.name
const existingTransform = name in math.expression.transform
const namespace = factory.path ? traverse(math, factory.path) : math
const existing = namespace.hasOwnProperty(name) ? namespace[name] : undefined
const resolver = function () {
let instance = load(factory)
if (instance && typeof instance.transform === 'function') {
throw new Error('Transforms cannot be attached to factory functions. ' +
'Please create a separate function for it with exports.path="expression.transform"')
}
if (isTypedFunction(existing) && isTypedFunction(instance)) {
if (options.override) {
// replace the existing typed function (nothing to do)
} else {
// merge the existing and new typed function
instance = typed(existing, instance)
}
return instance
}
if (existing === undefined || options.override) {
return instance
}
if (!options.silent) {
throw new Error('Cannot import "' + name + '": already exists')
}
}
if (factory.lazy !== false) {
lazy(namespace, name, resolver)
if (existingTransform) {
_deleteTransform(name)
} else {
if (factory.path === 'expression.transform' || factoryAllowedInExpressions(factory)) {
lazy(math.expression.mathWithTransform, name, resolver)
}
}
} else {
namespace[name] = resolver()
if (existingTransform) {
_deleteTransform(name)
} else {
if (factory.path === 'expression.transform' || factoryAllowedInExpressions(factory)) {
math.expression.mathWithTransform[name] = resolver()
}
}
}
math.emit('import', name, resolver, factory.path)
} else {
// unnamed factory.
// no lazy loading
load(factory)
}
}
/**
* Check whether given object is a type which can be imported
* @param {Function | number | string | boolean | null | Unit | Complex} object
* @return {boolean}
* @private
*/
function isSupportedType (object) {
return typeof object === 'function' ||
typeof object === 'number' ||
typeof object === 'string' ||
typeof object === 'boolean' ||
object === null ||
(object && type.isUnit(object)) ||
(object && type.isComplex(object)) ||
(object && type.isBigNumber(object)) ||
(object && type.isFraction(object)) ||
(object && type.isMatrix(object)) ||
(object && Array.isArray(object))
}
/**
* Test whether a given thing is a typed-function
* @param {*} fn
* @return {boolean} Returns true when `fn` is a typed-function
*/
function isTypedFunction (fn) {
return typeof fn === 'function' && typeof fn.signatures === 'object'
}
function allowedInExpressions (name) {
return !unsafe.hasOwnProperty(name)
}
function factoryAllowedInExpressions (factory) {
return factory.path === undefined && !unsafe.hasOwnProperty(factory.name)
}
// namespaces and functions not available in the parser for safety reasons
const unsafe = {
'expression': true,
'type': true,
'docs': true,
'error': true,
'json': true,
'chain': true // chain method not supported. Note that there is a unit chain too.
}
return mathImport
}
exports.math = true // request access to the math namespace as 5th argument of the factory function
exports.name = 'import'
exports.factory = factory
exports.lazy = true