UNPKG

9.98 kBJavaScriptView Raw
1import './../utils/polyfills'
2import { deepFlatten, isLegacyFactory, lazy, traverse, values } from './../utils/object'
3import * as emitter from './../utils/emitter'
4import { importFactory } from './function/import'
5import { configFactory } from './function/config'
6import { factory, isFactory } from '../utils/factory'
7import {
8 isAccessorNode,
9 isArray,
10 isArrayNode,
11 isAssignmentNode,
12 isBigNumber,
13 isBlockNode,
14 isBoolean,
15 isChain,
16 isCollection,
17 isComplex,
18 isConditionalNode,
19 isConstantNode,
20 isDate,
21 isDenseMatrix,
22 isFraction,
23 isFunction,
24 isFunctionAssignmentNode,
25 isFunctionNode,
26 isHelp,
27 isIndex,
28 isIndexNode,
29 isMatrix,
30 isNode,
31 isNull,
32 isNumber,
33 isObject,
34 isObjectNode,
35 isOperatorNode,
36 isParenthesisNode,
37 isRange,
38 isRangeNode,
39 isRegExp,
40 isResultSet,
41 isSparseMatrix,
42 isString,
43 isSymbolNode,
44 isUndefined,
45 isUnit
46} from '../utils/is'
47import { initial, last } from '../utils/array'
48import { warnOnce } from '../utils/log'
49import { ArgumentsError } from '../error/ArgumentsError'
50import { DimensionError } from '../error/DimensionError'
51import { IndexError } from '../error/IndexError'
52import { DEFAULT_CONFIG } from './config'
53
54/**
55 * Create a mathjs instance from given factory functions and optionally config
56 *
57 * Usage:
58 *
59 * const mathjs1 = create({ createAdd, createMultiply, ...})
60 * const config = { number: 'BigNumber' }
61 * const mathjs2 = create(all, config)
62 *
63 * @param {Object} [factories] An object with factory functions
64 * The object can contain nested objects,
65 * all nested objects will be flattened.
66 * @param {Object} [config] Available options:
67 * {number} epsilon
68 * Minimum relative difference between two
69 * compared values, used by all comparison functions.
70 * {string} matrix
71 * A string 'Matrix' (default) or 'Array'.
72 * {string} number
73 * A string 'number' (default), 'BigNumber', or 'Fraction'
74 * {number} precision
75 * The number of significant digits for BigNumbers.
76 * Not applicable for Numbers.
77 * {boolean} predictable
78 * Predictable output type of functions. When true,
79 * output type depends only on the input types. When
80 * false (default), output type can vary depending
81 * on input values. For example `math.sqrt(-4)`
82 * returns `complex('2i')` when predictable is false, and
83 * returns `NaN` when true.
84 * {string} randomSeed
85 * Random seed for seeded pseudo random number generator.
86 * Set to null to randomly seed.
87 * @returns {Object} Returns a bare-bone math.js instance containing
88 * functions:
89 * - `import` to add new functions
90 * - `config` to change configuration
91 * - `on`, `off`, `once`, `emit` for events
92 */
93export function create (factories, config) {
94 const configInternal = Object.assign({}, DEFAULT_CONFIG, config)
95
96 // simple test for ES5 support
97 if (typeof Object.create !== 'function') {
98 throw new Error('ES5 not supported by this JavaScript engine. ' +
99 'Please load the es5-shim and es5-sham library for compatibility.')
100 }
101
102 // create the mathjs instance
103 const math = emitter.mixin({
104 // only here for backward compatibility for legacy factory functions
105 isNumber,
106 isComplex,
107 isBigNumber,
108 isFraction,
109 isUnit,
110 isString,
111 isArray,
112 isMatrix,
113 isCollection,
114 isDenseMatrix,
115 isSparseMatrix,
116 isRange,
117 isIndex,
118 isBoolean,
119 isResultSet,
120 isHelp,
121 isFunction,
122 isDate,
123 isRegExp,
124 isObject,
125 isNull,
126 isUndefined,
127
128 isAccessorNode,
129 isArrayNode,
130 isAssignmentNode,
131 isBlockNode,
132 isConditionalNode,
133 isConstantNode,
134 isFunctionAssignmentNode,
135 isFunctionNode,
136 isIndexNode,
137 isNode,
138 isObjectNode,
139 isOperatorNode,
140 isParenthesisNode,
141 isRangeNode,
142 isSymbolNode,
143
144 isChain
145 })
146
147 // load config function and apply provided config
148 math.config = configFactory(configInternal, math.emit)
149
150 math.expression = {
151 transform: {},
152 mathWithTransform: {
153 config: math.config
154 }
155 }
156
157 // cached factories and instances used by function load
158 const legacyFactories = []
159 const legacyInstances = []
160
161 /**
162 * Load a function or data type from a factory.
163 * If the function or data type already exists, the existing instance is
164 * returned.
165 * @param {Function} factory
166 * @returns {*}
167 */
168 function load (factory) {
169 if (isFactory(factory)) {
170 return factory(math)
171 }
172
173 const firstProperty = factory[Object.keys(factory)[0]]
174 if (isFactory(firstProperty)) {
175 return firstProperty(math)
176 }
177
178 if (!isLegacyFactory(factory)) {
179 console.warn('Factory object with properties `type`, `name`, and `factory` expected', factory)
180 throw new Error('Factory object with properties `type`, `name`, and `factory` expected')
181 }
182
183 const index = legacyFactories.indexOf(factory)
184 let instance
185 if (index === -1) {
186 // doesn't yet exist
187 if (factory.math === true) {
188 // pass with math namespace
189 instance = factory.factory(math.type, configInternal, load, math.typed, math)
190 } else {
191 instance = factory.factory(math.type, configInternal, load, math.typed)
192 }
193
194 // append to the cache
195 legacyFactories.push(factory)
196 legacyInstances.push(instance)
197 } else {
198 // already existing function, return the cached instance
199 instance = legacyInstances[index]
200 }
201
202 return instance
203 }
204
205 const importedFactories = {}
206
207 // load the import function
208 function lazyTyped (...args) {
209 return math.typed.apply(math.typed, args)
210 }
211 const internalImport = importFactory(lazyTyped, load, math, importedFactories)
212 math.import = internalImport
213
214 // listen for changes in config, import all functions again when changed
215 // TODO: move this listener into the import function?
216 math.on('config', () => {
217 values(importedFactories).forEach(factory => {
218 if (factory && factory.meta && factory.meta.recreateOnConfigChange) {
219 // FIXME: only re-create when the current instance is the same as was initially created
220 // FIXME: delete the functions/constants before importing them again?
221 internalImport(factory, { override: true })
222 }
223 })
224 })
225
226 // the create function exposed on the mathjs instance is bound to
227 // the factory functions passed before
228 math.create = create.bind(null, factories)
229
230 // export factory function
231 math.factory = factory
232
233 // import the factory functions like createAdd as an array instead of object,
234 // else they will get a different naming (`createAdd` instead of `add`).
235 math.import(values(deepFlatten(factories)))
236
237 // TODO: deprecated since v6.0.0. Clean up some day
238 const movedNames = [
239 'type.isNumber',
240 'type.isComplex',
241 'type.isBigNumber',
242 'type.isFraction',
243 'type.isUnit',
244 'type.isString',
245 'type.isArray',
246 'type.isMatrix',
247 'type.isDenseMatrix',
248 'type.isSparseMatrix',
249 'type.isCollection',
250 'type.isRange',
251 'type.isIndex',
252 'type.isBoolean',
253 'type.isResultSet',
254 'type.isHelp',
255 'type.isFunction',
256 'type.isDate',
257 'type.isRegExp',
258 'type.isObject',
259 'type.isNull',
260 'type.isUndefined',
261 'type.isAccessorNode',
262 'type.isArrayNode',
263 'type.isAssignmentNode',
264 'type.isBlockNode',
265 'type.isConditionalNode',
266 'type.isConstantNode',
267 'type.isFunctionAssignmentNode',
268 'type.isFunctionNode',
269 'type.isIndexNode',
270 'type.isNode',
271 'type.isObjectNode',
272 'type.isOperatorNode',
273 'type.isParenthesisNode',
274 'type.isRangeNode',
275 'type.isSymbolNode',
276 'type.isChain',
277 'type.BigNumber',
278 'type.Chain',
279 'type.Complex',
280 'type.Fraction',
281 'type.Matrix',
282 'type.DenseMatrix',
283 'type.SparseMatrix',
284 'type.Spa',
285 'type.FibonacciHeap',
286 'type.ImmutableDenseMatrix',
287 'type.Index',
288 'type.Range',
289 'type.ResultSet',
290 'type.Unit',
291 'type.Help',
292 'type.Parser',
293 'expression.parse',
294 'expression.Parser',
295 'expression.node.AccessorNode',
296 'expression.node.ArrayNode',
297 'expression.node.AssignmentNode',
298 'expression.node.BlockNode',
299 'expression.node.ConditionalNode',
300 'expression.node.ConstantNode',
301 'expression.node.IndexNode',
302 'expression.node.FunctionAssignmentNode',
303 'expression.node.FunctionNode',
304 'expression.node.Node',
305 'expression.node.ObjectNode',
306 'expression.node.OperatorNode',
307 'expression.node.ParenthesisNode',
308 'expression.node.RangeNode',
309 'expression.node.RelationalNode',
310 'expression.node.SymbolNode',
311 'json.reviver',
312 'error.ArgumentsError',
313 'error.DimensionError',
314 'error.IndexError'
315 ]
316 movedNames.forEach(fullName => {
317 const parts = fullName.split('.')
318
319 const path = initial(parts)
320 const name = last(parts)
321 const obj = traverse(math, path)
322
323 lazy(obj, name, () => {
324 warnOnce(`math.${fullName} is moved to math.${name} in v6.0.0. ` +
325 'Please use the new location instead.')
326 return math[name]
327 })
328 })
329 lazy(math.expression, 'docs', () => {
330 throw new Error('math.expression.docs has been moved. ' +
331 'Please import via "import { docs } from \'mathjs\'"')
332 })
333
334 math.ArgumentsError = ArgumentsError
335 math.DimensionError = DimensionError
336 math.IndexError = IndexError
337
338 return math
339}