UNPKG

9.58 kBJavaScriptView Raw
1import { isBigNumber, isConstantNode, isNode, isRangeNode, isSymbolNode } from '../../utils/is.js';
2import { map } from '../../utils/array.js';
3import { escape } from '../../utils/string.js';
4import { factory } from '../../utils/factory.js';
5import { getSafeProperty } from '../../utils/customs.js';
6var name = 'IndexNode';
7var dependencies = ['Range', 'Node', 'size'];
8export var createIndexNode = /* #__PURE__ */factory(name, dependencies, (_ref) => {
9 var {
10 Range,
11 Node,
12 size
13 } = _ref;
14
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] Optional property describing whether
25 * this index was written using dot
26 * notation like `a.b`, or using bracket
27 * notation like `a["b"]` (default).
28 * Used to stringify an IndexNode.
29 */
30 function IndexNode(dimensions, dotNotation) {
31 if (!(this instanceof IndexNode)) {
32 throw new SyntaxError('Constructor must be called with the new operator');
33 }
34
35 this.dimensions = dimensions;
36 this.dotNotation = dotNotation || false; // validate input
37
38 if (!Array.isArray(dimensions) || !dimensions.every(isNode)) {
39 throw new TypeError('Array containing Nodes expected for parameter "dimensions"');
40 }
41
42 if (this.dotNotation && !this.isObjectProperty()) {
43 throw new Error('dotNotation only applicable for object properties');
44 }
45 }
46
47 IndexNode.prototype = new Node();
48 IndexNode.prototype.type = 'IndexNode';
49 IndexNode.prototype.isIndexNode = true;
50 /**
51 * Compile a node into a JavaScript function.
52 * This basically pre-calculates as much as possible and only leaves open
53 * calculations which depend on a dynamic scope with variables.
54 * @param {Object} math Math.js namespace with functions and constants.
55 * @param {Object} argNames An object with argument names as key and `true`
56 * as value. Used in the SymbolNode to optimize
57 * for arguments from user assigned functions
58 * (see FunctionAssignmentNode) or special symbols
59 * like `end` (see IndexNode).
60 * @return {function} Returns a function which can be called like:
61 * evalNode(scope: Object, args: Object, context: *)
62 */
63
64 IndexNode.prototype._compile = function (math, argNames) {
65 // TODO: implement support for bignumber (currently bignumbers are silently
66 // reduced to numbers when changing the value to zero-based)
67 // TODO: Optimization: when the range values are ConstantNodes,
68 // we can beforehand resolve the zero-based value
69 // optimization for a simple object property
70 var evalDimensions = map(this.dimensions, function (range, i) {
71 if (isRangeNode(range)) {
72 if (range.needsEnd()) {
73 // create a range containing end (like '4:end')
74 var childArgNames = Object.create(argNames);
75 childArgNames.end = true;
76
77 var evalStart = range.start._compile(math, childArgNames);
78
79 var evalEnd = range.end._compile(math, childArgNames);
80
81 var evalStep = range.step ? range.step._compile(math, childArgNames) : function () {
82 return 1;
83 };
84 return function evalDimension(scope, args, context) {
85 var s = size(context).valueOf();
86 var childArgs = Object.create(args);
87 childArgs.end = s[i];
88 return createRange(evalStart(scope, childArgs, context), evalEnd(scope, childArgs, context), evalStep(scope, childArgs, context));
89 };
90 } else {
91 // create range
92 var _evalStart = range.start._compile(math, argNames);
93
94 var _evalEnd = range.end._compile(math, argNames);
95
96 var _evalStep = range.step ? range.step._compile(math, argNames) : function () {
97 return 1;
98 };
99
100 return function evalDimension(scope, args, context) {
101 return createRange(_evalStart(scope, args, context), _evalEnd(scope, args, context), _evalStep(scope, args, context));
102 };
103 }
104 } else if (isSymbolNode(range) && range.name === 'end') {
105 // SymbolNode 'end'
106 var _childArgNames = Object.create(argNames);
107
108 _childArgNames.end = true;
109
110 var evalRange = range._compile(math, _childArgNames);
111
112 return function evalDimension(scope, args, context) {
113 var s = size(context).valueOf();
114 var childArgs = Object.create(args);
115 childArgs.end = s[i];
116 return evalRange(scope, childArgs, context);
117 };
118 } else {
119 // ConstantNode
120 var _evalRange = range._compile(math, argNames);
121
122 return function evalDimension(scope, args, context) {
123 return _evalRange(scope, args, context);
124 };
125 }
126 });
127 var index = getSafeProperty(math, 'index');
128 return function evalIndexNode(scope, args, context) {
129 var dimensions = map(evalDimensions, function (evalDimension) {
130 return evalDimension(scope, args, context);
131 });
132 return index(...dimensions);
133 };
134 };
135 /**
136 * Execute a callback for each of the child nodes of this node
137 * @param {function(child: Node, path: string, parent: Node)} callback
138 */
139
140
141 IndexNode.prototype.forEach = function (callback) {
142 for (var i = 0; i < this.dimensions.length; i++) {
143 callback(this.dimensions[i], 'dimensions[' + i + ']', this);
144 }
145 };
146 /**
147 * Create a new IndexNode having it's childs be the results of calling
148 * the provided callback function for each of the childs of the original node.
149 * @param {function(child: Node, path: string, parent: Node): Node} callback
150 * @returns {IndexNode} Returns a transformed copy of the node
151 */
152
153
154 IndexNode.prototype.map = function (callback) {
155 var dimensions = [];
156
157 for (var i = 0; i < this.dimensions.length; i++) {
158 dimensions[i] = this._ifNode(callback(this.dimensions[i], 'dimensions[' + i + ']', this));
159 }
160
161 return new IndexNode(dimensions, this.dotNotation);
162 };
163 /**
164 * Create a clone of this node, a shallow copy
165 * @return {IndexNode}
166 */
167
168
169 IndexNode.prototype.clone = function () {
170 return new IndexNode(this.dimensions.slice(0), this.dotNotation);
171 };
172 /**
173 * Test whether this IndexNode contains a single property name
174 * @return {boolean}
175 */
176
177
178 IndexNode.prototype.isObjectProperty = function () {
179 return this.dimensions.length === 1 && isConstantNode(this.dimensions[0]) && typeof this.dimensions[0].value === 'string';
180 };
181 /**
182 * Returns the property name if IndexNode contains a property.
183 * If not, returns null.
184 * @return {string | null}
185 */
186
187
188 IndexNode.prototype.getObjectProperty = function () {
189 return this.isObjectProperty() ? this.dimensions[0].value : null;
190 };
191 /**
192 * Get string representation
193 * @param {Object} options
194 * @return {string} str
195 */
196
197
198 IndexNode.prototype._toString = function (options) {
199 // format the parameters like "[1, 0:5]"
200 return this.dotNotation ? '.' + this.getObjectProperty() : '[' + this.dimensions.join(', ') + ']';
201 };
202 /**
203 * Get a JSON representation of the node
204 * @returns {Object}
205 */
206
207
208 IndexNode.prototype.toJSON = function () {
209 return {
210 mathjs: 'IndexNode',
211 dimensions: this.dimensions,
212 dotNotation: this.dotNotation
213 };
214 };
215 /**
216 * Instantiate an IndexNode from its JSON representation
217 * @param {Object} json An object structured like
218 * `{"mathjs": "IndexNode", dimensions: [...], dotNotation: false}`,
219 * where mathjs is optional
220 * @returns {IndexNode}
221 */
222
223
224 IndexNode.fromJSON = function (json) {
225 return new IndexNode(json.dimensions, json.dotNotation);
226 };
227 /**
228 * Get HTML representation
229 * @param {Object} options
230 * @return {string} str
231 */
232
233
234 IndexNode.prototype.toHTML = function (options) {
235 // format the parameters like "[1, 0:5]"
236 var dimensions = [];
237
238 for (var i = 0; i < this.dimensions.length; i++) {
239 dimensions[i] = this.dimensions[i].toHTML();
240 }
241
242 if (this.dotNotation) {
243 return '<span class="math-operator math-accessor-operator">.</span>' + '<span class="math-symbol math-property">' + escape(this.getObjectProperty()) + '</span>';
244 } else {
245 return '<span class="math-parenthesis math-square-parenthesis">[</span>' + dimensions.join('<span class="math-separator">,</span>') + '<span class="math-parenthesis math-square-parenthesis">]</span>';
246 }
247 };
248 /**
249 * Get LaTeX representation
250 * @param {Object} options
251 * @return {string} str
252 */
253
254
255 IndexNode.prototype._toTex = function (options) {
256 var dimensions = this.dimensions.map(function (range) {
257 return range.toTex(options);
258 });
259 return this.dotNotation ? '.' + this.getObjectProperty() + '' : '_{' + dimensions.join(',') + '}';
260 }; // helper function to create a Range from start, step and end
261
262
263 function createRange(start, end, step) {
264 return new Range(isBigNumber(start) ? start.toNumber() : start, isBigNumber(end) ? end.toNumber() : end, isBigNumber(step) ? step.toNumber() : step);
265 }
266
267 return IndexNode;
268}, {
269 isClass: true,
270 isNode: true
271});
\No newline at end of file