123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361 |
- import { isNode } from '../../utils/is.js';
- import { keywords } from '../keywords.js';
- import { deepStrictEqual } from '../../utils/object.js';
- import { factory } from '../../utils/factory.js';
- import { createMap } from '../../utils/map.js';
- var name = 'Node';
- var dependencies = ['mathWithTransform'];
- export var createNode = /* #__PURE__ */factory(name, dependencies, _ref => {
- var {
- mathWithTransform
- } = _ref;
- /**
- * Validate the symbol names of a scope.
- * Throws an error when the scope contains an illegal symbol.
- * @param {Object} scope
- */
- function _validateScope(scope) {
- for (var symbol of [...keywords]) {
- if (scope.has(symbol)) {
- throw new Error('Scope contains an illegal symbol, "' + symbol + '" is a reserved keyword');
- }
- }
- }
- class Node {
- get type() {
- return 'Node';
- }
- get isNode() {
- return true;
- }
- /**
- * Evaluate the node
- * @param {Object} [scope] Scope to read/write variables
- * @return {*} Returns the result
- */
- evaluate(scope) {
- return this.compile().evaluate(scope);
- }
- /**
- * Compile the node into an optimized, evauatable JavaScript function
- * @return {{evaluate: function([Object])}} object
- * Returns an object with a function 'evaluate',
- * which can be invoked as expr.evaluate([scope: Object]),
- * where scope is an optional object with
- * variables.
- */
- compile() {
- var expr = this._compile(mathWithTransform, {});
- var args = {};
- var context = null;
- function evaluate(scope) {
- var s = createMap(scope);
- _validateScope(s);
- return expr(s, args, context);
- }
- return {
- evaluate
- };
- }
- /**
- * 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) {
- throw new Error('Method _compile must be implemented by type ' + this.type);
- }
- /**
- * Execute a callback for each of the child nodes of this node
- * @param {function(child: Node, path: string, parent: Node)} callback
- */
- forEach(callback) {
- // must be implemented by each of the Node implementations
- throw new Error('Cannot run forEach on a Node interface');
- }
- /**
- * Create a new Node whose children are the results of calling the
- * provided callback function for each child of the original node.
- * @param {function(child: Node, path: string, parent: Node): Node} callback
- * @returns {OperatorNode} Returns a transformed copy of the node
- */
- map(callback) {
- // must be implemented by each of the Node implementations
- throw new Error('Cannot run map on a Node interface');
- }
- /**
- * Validate whether an object is a Node, for use with map
- * @param {Node} node
- * @returns {Node} Returns the input if it's a node, else throws an Error
- * @protected
- */
- _ifNode(node) {
- if (!isNode(node)) {
- throw new TypeError('Callback function must return a Node');
- }
- return node;
- }
- /**
- * Recursively traverse all nodes in a node tree. Executes given callback for
- * this node and each of its child nodes.
- * @param {function(node: Node, path: string, parent: Node)} callback
- * A callback called for every node in the node tree.
- */
- traverse(callback) {
- // execute callback for itself
- // eslint-disable-next-line
- callback(this, null, null);
- // recursively traverse over all children of a node
- function _traverse(node, callback) {
- node.forEach(function (child, path, parent) {
- callback(child, path, parent);
- _traverse(child, callback);
- });
- }
- _traverse(this, callback);
- }
- /**
- * Recursively transform a node tree via a transform function.
- *
- * For example, to replace all nodes of type SymbolNode having name 'x' with
- * a ConstantNode with value 2:
- *
- * const res = Node.transform(function (node, path, parent) {
- * if (node && node.isSymbolNode) && (node.name === 'x')) {
- * return new ConstantNode(2)
- * }
- * else {
- * return node
- * }
- * })
- *
- * @param {function(node: Node, path: string, parent: Node) : Node} callback
- * A mapping function accepting a node, and returning
- * a replacement for the node or the original node. The "signature"
- * of the callback must be:
- * callback(node: Node, index: string, parent: Node) : Node
- * @return {Node} Returns the original node or its replacement
- */
- transform(callback) {
- function _transform(child, path, parent) {
- var replacement = callback(child, path, parent);
- if (replacement !== child) {
- // stop iterating when the node is replaced
- return replacement;
- }
- return child.map(_transform);
- }
- return _transform(this, null, null);
- }
- /**
- * Find any node in the node tree matching given filter function. For
- * example, to find all nodes of type SymbolNode having name 'x':
- *
- * const results = Node.filter(function (node) {
- * return (node && node.isSymbolNode) && (node.name === 'x')
- * })
- *
- * @param {function(node: Node, path: string, parent: Node) : Node} callback
- * A test function returning true when a node matches, and false
- * otherwise. Function signature:
- * callback(node: Node, index: string, parent: Node) : boolean
- * @return {Node[]} nodes
- * An array with nodes matching given filter criteria
- */
- filter(callback) {
- var nodes = [];
- this.traverse(function (node, path, parent) {
- if (callback(node, path, parent)) {
- nodes.push(node);
- }
- });
- return nodes;
- }
- /**
- * Create a shallow clone of this node
- * @return {Node}
- */
- clone() {
- // must be implemented by each of the Node implementations
- throw new Error('Cannot clone a Node interface');
- }
- /**
- * Create a deep clone of this node
- * @return {Node}
- */
- cloneDeep() {
- return this.map(function (node) {
- return node.cloneDeep();
- });
- }
- /**
- * Deep compare this node with another node.
- * @param {Node} other
- * @return {boolean} Returns true when both nodes are of the same type and
- * contain the same values (as do their childs)
- */
- equals(other) {
- return other ? this.type === other.type && deepStrictEqual(this, other) : false;
- }
- /**
- * Get string representation. (wrapper function)
- *
- * This function can get an object of the following form:
- * {
- * handler: //This can be a callback function of the form
- * // "function callback(node, options)"or
- * // a map that maps function names (used in FunctionNodes)
- * // to callbacks
- * parenthesis: "keep" //the parenthesis option (This is optional)
- * }
- *
- * @param {Object} [options]
- * @return {string}
- */
- toString(options) {
- var customString = this._getCustomString(options);
- if (typeof customString !== 'undefined') {
- return customString;
- }
- return this._toString(options);
- }
- /**
- * Get a JSON representation of the node
- * Both .toJSON() and the static .fromJSON(json) should be implemented by all
- * implementations of Node
- * @returns {Object}
- */
- toJSON() {
- throw new Error('Cannot serialize object: toJSON not implemented by ' + this.type);
- }
- /**
- * Get HTML representation. (wrapper function)
- *
- * This function can get an object of the following form:
- * {
- * handler: //This can be a callback function of the form
- * // "function callback(node, options)" or
- * // a map that maps function names (used in FunctionNodes)
- * // to callbacks
- * parenthesis: "keep" //the parenthesis option (This is optional)
- * }
- *
- * @param {Object} [options]
- * @return {string}
- */
- toHTML(options) {
- var customString = this._getCustomString(options);
- if (typeof customString !== 'undefined') {
- return customString;
- }
- return this.toHTML(options);
- }
- /**
- * Internal function to generate the string output.
- * This has to be implemented by every Node
- *
- * @throws {Error}
- */
- _toString() {
- // must be implemented by each of the Node implementations
- throw new Error('_toString not implemented for ' + this.type);
- }
- /**
- * Get LaTeX representation. (wrapper function)
- *
- * This function can get an object of the following form:
- * {
- * handler: //This can be a callback function of the form
- * // "function callback(node, options)"or
- * // a map that maps function names (used in FunctionNodes)
- * // to callbacks
- * parenthesis: "keep" //the parenthesis option (This is optional)
- * }
- *
- * @param {Object} [options]
- * @return {string}
- */
- toTex(options) {
- var customString = this._getCustomString(options);
- if (typeof customString !== 'undefined') {
- return customString;
- }
- return this._toTex(options);
- }
- /**
- * Internal function to generate the LaTeX output.
- * This has to be implemented by every Node
- *
- * @param {Object} [options]
- * @throws {Error}
- */
- _toTex(options) {
- // must be implemented by each of the Node implementations
- throw new Error('_toTex not implemented for ' + this.type);
- }
- /**
- * Helper used by `to...` functions.
- */
- _getCustomString(options) {
- if (options && typeof options === 'object') {
- switch (typeof options.handler) {
- case 'object':
- case 'undefined':
- return;
- case 'function':
- return options.handler(this, options);
- default:
- throw new TypeError('Object or function expected as callback');
- }
- }
- }
- /**
- * Get identifier.
- * @return {string}
- */
- getIdentifier() {
- return this.type;
- }
- /**
- * Get the content of the current Node.
- * @return {Node} node
- **/
- getContent() {
- return this;
- }
- }
- return Node;
- }, {
- isClass: true,
- isNode: true
- });
|