IndexNode.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. import _defineProperty from "@babel/runtime/helpers/defineProperty";
  2. import { map } from '../../utils/array.js';
  3. import { getSafeProperty } from '../../utils/customs.js';
  4. import { factory } from '../../utils/factory.js';
  5. import { isArray, isConstantNode, isMatrix, isNode, isString, typeOf } from '../../utils/is.js';
  6. import { escape } from '../../utils/string.js';
  7. var name = 'IndexNode';
  8. var dependencies = ['Node', 'size'];
  9. export var createIndexNode = /* #__PURE__ */factory(name, dependencies, _ref => {
  10. var {
  11. Node,
  12. size
  13. } = _ref;
  14. class IndexNode extends Node {
  15. /**
  16. * @constructor IndexNode
  17. * @extends Node
  18. *
  19. * Describes a subset of a matrix or an object property.
  20. * Cannot be used on its own, needs to be used within an AccessorNode or
  21. * AssignmentNode.
  22. *
  23. * @param {Node[]} dimensions
  24. * @param {boolean} [dotNotation=false]
  25. * Optional property describing whether this index was written using dot
  26. * notation like `a.b`, or using bracket notation like `a["b"]`
  27. * (which is the default). This property is used for string conversion.
  28. */
  29. constructor(dimensions, dotNotation) {
  30. super();
  31. this.dimensions = dimensions;
  32. this.dotNotation = dotNotation || false;
  33. // validate input
  34. if (!Array.isArray(dimensions) || !dimensions.every(isNode)) {
  35. throw new TypeError('Array containing Nodes expected for parameter "dimensions"');
  36. }
  37. if (this.dotNotation && !this.isObjectProperty()) {
  38. throw new Error('dotNotation only applicable for object properties');
  39. }
  40. }
  41. get type() {
  42. return name;
  43. }
  44. get isIndexNode() {
  45. return true;
  46. }
  47. /**
  48. * Compile a node into a JavaScript function.
  49. * This basically pre-calculates as much as possible and only leaves open
  50. * calculations which depend on a dynamic scope with variables.
  51. * @param {Object} math Math.js namespace with functions and constants.
  52. * @param {Object} argNames An object with argument names as key and `true`
  53. * as value. Used in the SymbolNode to optimize
  54. * for arguments from user assigned functions
  55. * (see FunctionAssignmentNode) or special symbols
  56. * like `end` (see IndexNode).
  57. * @return {function} Returns a function which can be called like:
  58. * evalNode(scope: Object, args: Object, context: *)
  59. */
  60. _compile(math, argNames) {
  61. // TODO: implement support for bignumber (currently bignumbers are silently
  62. // reduced to numbers when changing the value to zero-based)
  63. // TODO: Optimization: when the range values are ConstantNodes,
  64. // we can beforehand resolve the zero-based value
  65. // optimization for a simple object property
  66. var evalDimensions = map(this.dimensions, function (dimension, i) {
  67. var needsEnd = dimension.filter(node => node.isSymbolNode && node.name === 'end').length > 0;
  68. if (needsEnd) {
  69. // SymbolNode 'end' is used inside the index,
  70. // like in `A[end]` or `A[end - 2]`
  71. var childArgNames = Object.create(argNames);
  72. childArgNames.end = true;
  73. var _evalDimension = dimension._compile(math, childArgNames);
  74. return function evalDimension(scope, args, context) {
  75. if (!isMatrix(context) && !isArray(context) && !isString(context)) {
  76. throw new TypeError('Cannot resolve "end": ' + 'context must be a Matrix, Array, or string but is ' + typeOf(context));
  77. }
  78. var s = size(context).valueOf();
  79. var childArgs = Object.create(args);
  80. childArgs.end = s[i];
  81. return _evalDimension(scope, childArgs, context);
  82. };
  83. } else {
  84. // SymbolNode `end` not used
  85. return dimension._compile(math, argNames);
  86. }
  87. });
  88. var index = getSafeProperty(math, 'index');
  89. return function evalIndexNode(scope, args, context) {
  90. var dimensions = map(evalDimensions, function (evalDimension) {
  91. return evalDimension(scope, args, context);
  92. });
  93. return index(...dimensions);
  94. };
  95. }
  96. /**
  97. * Execute a callback for each of the child nodes of this node
  98. * @param {function(child: Node, path: string, parent: Node)} callback
  99. */
  100. forEach(callback) {
  101. for (var i = 0; i < this.dimensions.length; i++) {
  102. callback(this.dimensions[i], 'dimensions[' + i + ']', this);
  103. }
  104. }
  105. /**
  106. * Create a new IndexNode whose children are the results of calling
  107. * the provided callback function for each child of the original node.
  108. * @param {function(child: Node, path: string, parent: Node): Node} callback
  109. * @returns {IndexNode} Returns a transformed copy of the node
  110. */
  111. map(callback) {
  112. var dimensions = [];
  113. for (var i = 0; i < this.dimensions.length; i++) {
  114. dimensions[i] = this._ifNode(callback(this.dimensions[i], 'dimensions[' + i + ']', this));
  115. }
  116. return new IndexNode(dimensions, this.dotNotation);
  117. }
  118. /**
  119. * Create a clone of this node, a shallow copy
  120. * @return {IndexNode}
  121. */
  122. clone() {
  123. return new IndexNode(this.dimensions.slice(0), this.dotNotation);
  124. }
  125. /**
  126. * Test whether this IndexNode contains a single property name
  127. * @return {boolean}
  128. */
  129. isObjectProperty() {
  130. return this.dimensions.length === 1 && isConstantNode(this.dimensions[0]) && typeof this.dimensions[0].value === 'string';
  131. }
  132. /**
  133. * Returns the property name if IndexNode contains a property.
  134. * If not, returns null.
  135. * @return {string | null}
  136. */
  137. getObjectProperty() {
  138. return this.isObjectProperty() ? this.dimensions[0].value : null;
  139. }
  140. /**
  141. * Get string representation
  142. * @param {Object} options
  143. * @return {string} str
  144. */
  145. _toString(options) {
  146. // format the parameters like "[1, 0:5]"
  147. return this.dotNotation ? '.' + this.getObjectProperty() : '[' + this.dimensions.join(', ') + ']';
  148. }
  149. /**
  150. * Get a JSON representation of the node
  151. * @returns {Object}
  152. */
  153. toJSON() {
  154. return {
  155. mathjs: name,
  156. dimensions: this.dimensions,
  157. dotNotation: this.dotNotation
  158. };
  159. }
  160. /**
  161. * Instantiate an IndexNode from its JSON representation
  162. * @param {Object} json
  163. * An object structured like
  164. * `{"mathjs": "IndexNode", dimensions: [...], dotNotation: false}`,
  165. * where mathjs is optional
  166. * @returns {IndexNode}
  167. */
  168. static fromJSON(json) {
  169. return new IndexNode(json.dimensions, json.dotNotation);
  170. }
  171. /**
  172. * Get HTML representation
  173. * @param {Object} options
  174. * @return {string} str
  175. */
  176. toHTML(options) {
  177. // format the parameters like "[1, 0:5]"
  178. var dimensions = [];
  179. for (var i = 0; i < this.dimensions.length; i++) {
  180. dimensions[i] = this.dimensions[i].toHTML();
  181. }
  182. if (this.dotNotation) {
  183. return '<span class="math-operator math-accessor-operator">.</span>' + '<span class="math-symbol math-property">' + escape(this.getObjectProperty()) + '</span>';
  184. } else {
  185. return '<span class="math-parenthesis math-square-parenthesis">[</span>' + dimensions.join('<span class="math-separator">,</span>') + '<span class="math-parenthesis math-square-parenthesis">]</span>';
  186. }
  187. }
  188. /**
  189. * Get LaTeX representation
  190. * @param {Object} options
  191. * @return {string} str
  192. */
  193. _toTex(options) {
  194. var dimensions = this.dimensions.map(function (range) {
  195. return range.toTex(options);
  196. });
  197. return this.dotNotation ? '.' + this.getObjectProperty() + '' : '_{' + dimensions.join(',') + '}';
  198. }
  199. }
  200. _defineProperty(IndexNode, "name", name);
  201. return IndexNode;
  202. }, {
  203. isClass: true,
  204. isNode: true
  205. });