UNPKG

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