UNPKG

12.6 kBJavaScriptView Raw
1function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
2
3import { isNode } from '../../utils/is';
4import { keywords } from '../keywords';
5import { deepStrictEqual, hasOwnProperty } from '../../utils/object';
6import { factory } from '../../utils/factory';
7var name = 'Node';
8var dependencies = ['mathWithTransform'];
9export var createNode = /* #__PURE__ */factory(name, dependencies, function (_ref) {
10 var mathWithTransform = _ref.mathWithTransform;
11
12 /**
13 * Node
14 */
15 function Node() {
16 if (!(this instanceof Node)) {
17 throw new SyntaxError('Constructor must be called with the new operator');
18 }
19 }
20 /**
21 * Evaluate the node
22 * @param {Object} [scope] Scope to read/write variables
23 * @return {*} Returns the result
24 */
25
26
27 Node.prototype.evaluate = function (scope) {
28 return this.compile().evaluate(scope);
29 };
30
31 Node.prototype.type = 'Node';
32 Node.prototype.isNode = true;
33 Node.prototype.comment = '';
34 /**
35 * Compile the node into an optimized, evauatable JavaScript function
36 * @return {{evaluate: function([Object])}} object
37 * Returns an object with a function 'evaluate',
38 * which can be invoked as expr.evaluate([scope: Object]),
39 * where scope is an optional object with
40 * variables.
41 */
42
43 Node.prototype.compile = function () {
44 var expr = this._compile(mathWithTransform, {});
45
46 var args = {};
47 var context = null;
48
49 function evaluate(scope) {
50 var s = scope || {};
51
52 _validateScope(s);
53
54 return expr(s, args, context);
55 }
56
57 return {
58 evaluate: evaluate
59 };
60 };
61 /**
62 * Compile a node into a JavaScript function.
63 * This basically pre-calculates as much as possible and only leaves open
64 * calculations which depend on a dynamic scope with variables.
65 * @param {Object} math Math.js namespace with functions and constants.
66 * @param {Object} argNames An object with argument names as key and `true`
67 * as value. Used in the SymbolNode to optimize
68 * for arguments from user assigned functions
69 * (see FunctionAssignmentNode) or special symbols
70 * like `end` (see IndexNode).
71 * @return {function} Returns a function which can be called like:
72 * evalNode(scope: Object, args: Object, context: *)
73 */
74
75
76 Node.prototype._compile = function (math, argNames) {
77 throw new Error('Method _compile should be implemented by type ' + this.type);
78 };
79 /**
80 * Execute a callback for each of the child nodes of this node
81 * @param {function(child: Node, path: string, parent: Node)} callback
82 */
83
84
85 Node.prototype.forEach = function (callback) {
86 // must be implemented by each of the Node implementations
87 throw new Error('Cannot run forEach on a Node interface');
88 };
89 /**
90 * Create a new Node having it's childs be the results of calling
91 * the provided callback function for each of the childs of the original node.
92 * @param {function(child: Node, path: string, parent: Node): Node} callback
93 * @returns {OperatorNode} Returns a transformed copy of the node
94 */
95
96
97 Node.prototype.map = function (callback) {
98 // must be implemented by each of the Node implementations
99 throw new Error('Cannot run map on a Node interface');
100 };
101 /**
102 * Validate whether an object is a Node, for use with map
103 * @param {Node} node
104 * @returns {Node} Returns the input if it's a node, else throws an Error
105 * @protected
106 */
107
108
109 Node.prototype._ifNode = function (node) {
110 if (!isNode(node)) {
111 throw new TypeError('Callback function must return a Node');
112 }
113
114 return node;
115 };
116 /**
117 * Recursively traverse all nodes in a node tree. Executes given callback for
118 * this node and each of its child nodes.
119 * @param {function(node: Node, path: string, parent: Node)} callback
120 * A callback called for every node in the node tree.
121 */
122
123
124 Node.prototype.traverse = function (callback) {
125 // execute callback for itself
126 callback(this, null, null); // eslint-disable-line standard/no-callback-literal
127 // recursively traverse over all childs of a node
128
129 function _traverse(node, callback) {
130 node.forEach(function (child, path, parent) {
131 callback(child, path, parent);
132
133 _traverse(child, callback);
134 });
135 }
136
137 _traverse(this, callback);
138 };
139 /**
140 * Recursively transform a node tree via a transform function.
141 *
142 * For example, to replace all nodes of type SymbolNode having name 'x' with a
143 * ConstantNode with value 2:
144 *
145 * const res = Node.transform(function (node, path, parent) {
146 * if (node && node.isSymbolNode) && (node.name === 'x')) {
147 * return new ConstantNode(2)
148 * }
149 * else {
150 * return node
151 * }
152 * })
153 *
154 * @param {function(node: Node, path: string, parent: Node) : Node} callback
155 * A mapping function accepting a node, and returning
156 * a replacement for the node or the original node.
157 * Signature: callback(node: Node, index: string, parent: Node) : Node
158 * @return {Node} Returns the original node or its replacement
159 */
160
161
162 Node.prototype.transform = function (callback) {
163 function _transform(child, path, parent) {
164 var replacement = callback(child, path, parent);
165
166 if (replacement !== child) {
167 // stop iterating when the node is replaced
168 return replacement;
169 }
170
171 return child.map(_transform);
172 }
173
174 return _transform(this, null, null);
175 };
176 /**
177 * Find any node in the node tree matching given filter function. For example, to
178 * find all nodes of type SymbolNode having name 'x':
179 *
180 * const results = Node.filter(function (node) {
181 * return (node && node.isSymbolNode) && (node.name === 'x')
182 * })
183 *
184 * @param {function(node: Node, path: string, parent: Node) : Node} callback
185 * A test function returning true when a node matches, and false
186 * otherwise. Function signature:
187 * callback(node: Node, index: string, parent: Node) : boolean
188 * @return {Node[]} nodes An array with nodes matching given filter criteria
189 */
190
191
192 Node.prototype.filter = function (callback) {
193 var nodes = [];
194 this.traverse(function (node, path, parent) {
195 if (callback(node, path, parent)) {
196 nodes.push(node);
197 }
198 });
199 return nodes;
200 };
201 /**
202 * Create a shallow clone of this node
203 * @return {Node}
204 */
205
206
207 Node.prototype.clone = function () {
208 // must be implemented by each of the Node implementations
209 throw new Error('Cannot clone a Node interface');
210 };
211 /**
212 * Create a deep clone of this node
213 * @return {Node}
214 */
215
216
217 Node.prototype.cloneDeep = function () {
218 return this.map(function (node) {
219 return node.cloneDeep();
220 });
221 };
222 /**
223 * Deep compare this node with another node.
224 * @param {Node} other
225 * @return {boolean} Returns true when both nodes are of the same type and
226 * contain the same values (as do their childs)
227 */
228
229
230 Node.prototype.equals = function (other) {
231 return other ? deepStrictEqual(this, other) : false;
232 };
233 /**
234 * Get string representation. (wrapper function)
235 *
236 * This function can get an object of the following form:
237 * {
238 * handler: //This can be a callback function of the form
239 * // "function callback(node, options)"or
240 * // a map that maps function names (used in FunctionNodes)
241 * // to callbacks
242 * parenthesis: "keep" //the parenthesis option (This is optional)
243 * }
244 *
245 * @param {Object} [options]
246 * @return {string}
247 */
248
249
250 Node.prototype.toString = function (options) {
251 var customString;
252
253 if (options && _typeof(options) === 'object') {
254 switch (_typeof(options.handler)) {
255 case 'object':
256 case 'undefined':
257 break;
258
259 case 'function':
260 customString = options.handler(this, options);
261 break;
262
263 default:
264 throw new TypeError('Object or function expected as callback');
265 }
266 }
267
268 if (typeof customString !== 'undefined') {
269 return customString;
270 }
271
272 return this._toString(options);
273 };
274 /**
275 * Get a JSON representation of the node
276 * Both .toJSON() and the static .fromJSON(json) should be implemented by all
277 * implementations of Node
278 * @returns {Object}
279 */
280
281
282 Node.prototype.toJSON = function () {
283 throw new Error('Cannot serialize object: toJSON not implemented by ' + this.type);
284 };
285 /**
286 * Get HTML representation. (wrapper function)
287 *
288 * This function can get an object of the following form:
289 * {
290 * handler: //This can be a callback function of the form
291 * // "function callback(node, options)" or
292 * // a map that maps function names (used in FunctionNodes)
293 * // to callbacks
294 * parenthesis: "keep" //the parenthesis option (This is optional)
295 * }
296 *
297 * @param {Object} [options]
298 * @return {string}
299 */
300
301
302 Node.prototype.toHTML = function (options) {
303 var customString;
304
305 if (options && _typeof(options) === 'object') {
306 switch (_typeof(options.handler)) {
307 case 'object':
308 case 'undefined':
309 break;
310
311 case 'function':
312 customString = options.handler(this, options);
313 break;
314
315 default:
316 throw new TypeError('Object or function expected as callback');
317 }
318 }
319
320 if (typeof customString !== 'undefined') {
321 return customString;
322 }
323
324 return this.toHTML(options);
325 };
326 /**
327 * Internal function to generate the string output.
328 * This has to be implemented by every Node
329 *
330 * @throws {Error}
331 */
332
333
334 Node.prototype._toString = function () {
335 // must be implemented by each of the Node implementations
336 throw new Error('_toString not implemented for ' + this.type);
337 };
338 /**
339 * Get LaTeX representation. (wrapper function)
340 *
341 * This function can get an object of the following form:
342 * {
343 * handler: //This can be a callback function of the form
344 * // "function callback(node, options)"or
345 * // a map that maps function names (used in FunctionNodes)
346 * // to callbacks
347 * parenthesis: "keep" //the parenthesis option (This is optional)
348 * }
349 *
350 * @param {Object} [options]
351 * @return {string}
352 */
353
354
355 Node.prototype.toTex = function (options) {
356 var customTex;
357
358 if (options && _typeof(options) === 'object') {
359 switch (_typeof(options.handler)) {
360 case 'object':
361 case 'undefined':
362 break;
363
364 case 'function':
365 customTex = options.handler(this, options);
366 break;
367
368 default:
369 throw new TypeError('Object or function expected as callback');
370 }
371 }
372
373 if (typeof customTex !== 'undefined') {
374 return customTex;
375 }
376
377 return this._toTex(options);
378 };
379 /**
380 * Internal function to generate the LaTeX output.
381 * This has to be implemented by every Node
382 *
383 * @param {Object} [options]
384 * @throws {Error}
385 */
386
387
388 Node.prototype._toTex = function (options) {
389 // must be implemented by each of the Node implementations
390 throw new Error('_toTex not implemented for ' + this.type);
391 };
392 /**
393 * Get identifier.
394 * @return {string}
395 */
396
397
398 Node.prototype.getIdentifier = function () {
399 return this.type;
400 };
401 /**
402 * Get the content of the current Node.
403 * @return {Node} node
404 **/
405
406
407 Node.prototype.getContent = function () {
408 return this;
409 };
410 /**
411 * Validate the symbol names of a scope.
412 * Throws an error when the scope contains an illegal symbol.
413 * @param {Object} scope
414 */
415
416
417 function _validateScope(scope) {
418 for (var symbol in scope) {
419 if (hasOwnProperty(scope, symbol)) {
420 if (symbol in keywords) {
421 throw new Error('Scope contains an illegal symbol, "' + symbol + '" is a reserved keyword');
422 }
423 }
424 }
425 }
426
427 return Node;
428}, {
429 isClass: true,
430 isNode: true
431});
\No newline at end of file