Node.js 11 KB

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