-
Notifications
You must be signed in to change notification settings - Fork 115
/
Copy patheval.mjs
117 lines (106 loc) · 3.17 KB
/
eval.mjs
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
import builtInMathEval from 'built-in-math-eval'
import intervalArithmeticEval from 'interval-arithmetic-eval'
const samplers = {
interval: intervalArithmeticEval,
builtIn: builtInMathEval
}
// getMathJS returns checks if mathjs is loaded.
function getMathJS() {
if (typeof global === 'object' && 'math' in global) {
// @ts-ignore
return global.math
}
if (typeof window === 'object' && 'math' in window) {
// @ts-ignore
return window.math
}
return null
}
const mathJS = getMathJS()
if (mathJS) {
// override the built-in module with mathjs's compile
samplers.builtIn = mathJS.compile
}
function generateEvaluator(samplerName) {
function doCompile(expression) {
// compiles does the following
//
// when expression === string
//
// gen = new require('math-codegen')
// return gen.parse(expression).compile(Interval|BultInMath)
//
// which is an object with the form
//
// {
// eval: function (scope) {
// // math-codegen magic
// }
// }
//
// when expression === function
//
// {
// eval: expression
// }
//
// otherwise throw an error
if (typeof expression === 'string') {
const compiled = samplers[samplerName](expression)
if (mathJS && samplerName === 'builtIn') {
// if mathjs is included use its evaluate method instead
return { eval: compiled.evaluate || compiled.eval }
}
return compiled
} else if (typeof expression === 'function') {
return { eval: expression }
} else {
throw Error('expression must be a string or a function')
}
}
function compileIfPossible(meta, property) {
// compile the function using interval arithmetic, cache the result
// so that multiple calls with the same argument don't trigger the
// kinda expensive compilation process
const expression = meta[property]
const hiddenProperty = samplerName + '_Expression_' + property
const hiddenCompiled = samplerName + '_Compiled_' + property
if (expression !== meta[hiddenProperty]) {
meta[hiddenProperty] = expression
meta[hiddenCompiled] = doCompile(expression)
}
}
function getCompiledExpression(meta, property) {
return meta[samplerName + '_Compiled_' + property]
}
/**
* Evaluates meta[property] with `variables`
*
* - Compiles meta[property] if it wasn't compiled already (also with cache
* check)
* - Evaluates the resulting function with the merge of meta.scope and
* `variables`
*
* @param meta
* @param property
* @param variables
* @returns The builtIn evaluator returns a number, the interval evaluator an array
*/
function evaluate(meta, property, variables) {
// e.g.
//
// meta: {
// fn: 'x + 3',
// scope: { y: 3 }
// }
// property: 'fn'
// variables: { x: 3 }
//
compileIfPossible(meta, property)
return getCompiledExpression(meta, property).eval(Object.assign({}, meta.scope || {}, variables))
}
return evaluate
}
const builtIn = generateEvaluator('builtIn')
const interval = generateEvaluator('interval')
export { builtIn, interval }