-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
SymbolNode.js
205 lines (185 loc) · 5.96 KB
/
SymbolNode.js
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
import { escape } from '../../utils/string.js'
import { getSafeProperty } from '../../utils/customs.js'
import { factory } from '../../utils/factory.js'
import { toSymbol } from '../../utils/latex.js'
const name = 'SymbolNode'
const dependencies = [
'math',
'?Unit',
'Node'
]
export const createSymbolNode = /* #__PURE__ */ factory(name, dependencies, ({ math, Unit, Node }) => {
/**
* Check whether some name is a valueless unit like "inch".
* @param {string} name
* @return {boolean}
*/
function isValuelessUnit (name) {
return Unit ? Unit.isValuelessUnit(name) : false
}
class SymbolNode extends Node {
/**
* @constructor SymbolNode
* @extends {Node}
* A symbol node can hold and resolve a symbol
* @param {string} name
* @extends {Node}
*/
constructor (name) {
super()
// validate input
if (typeof name !== 'string') {
throw new TypeError('String expected for parameter "name"')
}
this.name = name
}
get type () { return 'SymbolNode' }
get isSymbolNode () { return true }
/**
* Compile a node into a JavaScript function.
* This basically pre-calculates as much as possible and only leaves open
* calculations which depend on a dynamic scope with variables.
* @param {Object} math Math.js namespace with functions and constants.
* @param {Object} argNames An object with argument names as key and `true`
* as value. Used in the SymbolNode to optimize
* for arguments from user assigned functions
* (see FunctionAssignmentNode) or special symbols
* like `end` (see IndexNode).
* @return {function} Returns a function which can be called like:
* evalNode(scope: Object, args: Object, context: *)
*/
_compile (math, argNames) {
const name = this.name
if (argNames[name] === true) {
// this is a FunctionAssignment argument
// (like an x when inside the expression of a function
// assignment `f(x) = ...`)
return function (scope, args, context) {
return getSafeProperty(args, name)
}
} else if (name in math) {
return function (scope, args, context) {
return scope.has(name)
? scope.get(name)
: getSafeProperty(math, name)
}
} else {
const isUnit = isValuelessUnit(name)
return function (scope, args, context) {
return scope.has(name)
? scope.get(name)
: isUnit
? new Unit(null, name)
: SymbolNode.onUndefinedSymbol(name)
}
}
}
/**
* Execute a callback for each of the child nodes of this node
* @param {function(child: Node, path: string, parent: Node)} callback
*/
forEach (callback) {
// nothing to do, we don't have any children
}
/**
* Create a new SymbolNode with children produced by the given callback.
* Trivial since a SymbolNode has no children
* @param {function(child: Node, path: string, parent: Node) : Node} callback
* @returns {SymbolNode} Returns a clone of the node
*/
map (callback) {
return this.clone()
}
/**
* Throws an error 'Undefined symbol {name}'
* @param {string} name
*/
static onUndefinedSymbol (name) {
throw new Error('Undefined symbol ' + name)
}
/**
* Create a clone of this node, a shallow copy
* @return {SymbolNode}
*/
clone () {
return new SymbolNode(this.name)
}
/**
* Get string representation
* @param {Object} options
* @return {string} str
* @override
*/
_toString (options) {
return this.name
}
/**
* Get HTML representation
* @param {Object} options
* @return {string} str
* @override
*/
toHTML (options) {
const name = escape(this.name)
if (name === 'true' || name === 'false') {
return '<span class="math-symbol math-boolean">' + name + '</span>'
} else if (name === 'i') {
return '<span class="math-symbol math-imaginary-symbol">' +
name + '</span>'
} else if (name === 'Infinity') {
return '<span class="math-symbol math-infinity-symbol">' +
name + '</span>'
} else if (name === 'NaN') {
return '<span class="math-symbol math-nan-symbol">' + name + '</span>'
} else if (name === 'null') {
return '<span class="math-symbol math-null-symbol">' + name + '</span>'
} else if (name === 'undefined') {
return '<span class="math-symbol math-undefined-symbol">' +
name + '</span>'
}
return '<span class="math-symbol">' + name + '</span>'
}
/**
* Get a JSON representation of the node
* @returns {Object}
*/
toJSON () {
return {
mathjs: 'SymbolNode',
name: this.name
}
}
/**
* Instantiate a SymbolNode from its JSON representation
* @param {Object} json An object structured like
* `{"mathjs": "SymbolNode", name: "x"}`,
* where mathjs is optional
* @returns {SymbolNode}
*/
static fromJSON (json) {
return new SymbolNode(json.name)
}
/**
* Get LaTeX representation
* @param {Object} options
* @return {string} str
* @override
*/
_toTex (options) {
let isUnit = false
if ((typeof math[this.name] === 'undefined') &&
isValuelessUnit(this.name)) {
isUnit = true
}
const symbol = toSymbol(this.name, isUnit)
if (symbol[0] === '\\') {
// no space needed if the symbol starts with '\'
return symbol
}
// the space prevents symbols from breaking stuff like '\cdot'
// if it's written right before the symbol
return ' ' + symbol
}
}
return SymbolNode
}, { isClass: true, isNode: true })