UNPKG

8.63 kBJavaScriptView Raw
1import { isNode, isSymbolNode } from '../../utils/is'
2import { factory } from '../../utils/factory'
3import { getPrecedence } from '../operators'
4
5const name = 'RangeNode'
6const dependencies = [
7 'Node'
8]
9
10export const createRangeNode = /* #__PURE__ */ factory(name, dependencies, ({ Node }) => {
11 /**
12 * @constructor RangeNode
13 * @extends {Node}
14 * create a range
15 * @param {Node} start included lower-bound
16 * @param {Node} end included upper-bound
17 * @param {Node} [step] optional step
18 */
19 function RangeNode (start, end, step) {
20 if (!(this instanceof RangeNode)) {
21 throw new SyntaxError('Constructor must be called with the new operator')
22 }
23
24 // validate inputs
25 if (!isNode(start)) throw new TypeError('Node expected')
26 if (!isNode(end)) throw new TypeError('Node expected')
27 if (step && !isNode(step)) throw new TypeError('Node expected')
28 if (arguments.length > 3) throw new Error('Too many arguments')
29
30 this.start = start // included lower-bound
31 this.end = end // included upper-bound
32 this.step = step || null // optional step
33 }
34
35 RangeNode.prototype = new Node()
36
37 RangeNode.prototype.type = 'RangeNode'
38
39 RangeNode.prototype.isRangeNode = true
40
41 /**
42 * Check whether the RangeNode needs the `end` symbol to be defined.
43 * This end is the size of the Matrix in current dimension.
44 * @return {boolean}
45 */
46 RangeNode.prototype.needsEnd = function () {
47 // find all `end` symbols in this RangeNode
48 const endSymbols = this.filter(function (node) {
49 return isSymbolNode(node) && (node.name === 'end')
50 })
51
52 return endSymbols.length > 0
53 }
54
55 /**
56 * Compile a node into a JavaScript function.
57 * This basically pre-calculates as much as possible and only leaves open
58 * calculations which depend on a dynamic scope with variables.
59 * @param {Object} math Math.js namespace with functions and constants.
60 * @param {Object} argNames An object with argument names as key and `true`
61 * as value. Used in the SymbolNode to optimize
62 * for arguments from user assigned functions
63 * (see FunctionAssignmentNode) or special symbols
64 * like `end` (see IndexNode).
65 * @return {function} Returns a function which can be called like:
66 * evalNode(scope: Object, args: Object, context: *)
67 */
68 RangeNode.prototype._compile = function (math, argNames) {
69 const range = math.range
70 const evalStart = this.start._compile(math, argNames)
71 const evalEnd = this.end._compile(math, argNames)
72
73 if (this.step) {
74 const evalStep = this.step._compile(math, argNames)
75
76 return function evalRangeNode (scope, args, context) {
77 return range(
78 evalStart(scope, args, context),
79 evalEnd(scope, args, context),
80 evalStep(scope, args, context)
81 )
82 }
83 } else {
84 return function evalRangeNode (scope, args, context) {
85 return range(
86 evalStart(scope, args, context),
87 evalEnd(scope, args, context)
88 )
89 }
90 }
91 }
92
93 /**
94 * Execute a callback for each of the child nodes of this node
95 * @param {function(child: Node, path: string, parent: Node)} callback
96 */
97 RangeNode.prototype.forEach = function (callback) {
98 callback(this.start, 'start', this)
99 callback(this.end, 'end', this)
100 if (this.step) {
101 callback(this.step, 'step', this)
102 }
103 }
104
105 /**
106 * Create a new RangeNode having it's childs be the results of calling
107 * the provided callback function for each of the childs of the original node.
108 * @param {function(child: Node, path: string, parent: Node): Node} callback
109 * @returns {RangeNode} Returns a transformed copy of the node
110 */
111 RangeNode.prototype.map = function (callback) {
112 return new RangeNode(
113 this._ifNode(callback(this.start, 'start', this)),
114 this._ifNode(callback(this.end, 'end', this)),
115 this.step && this._ifNode(callback(this.step, 'step', this))
116 )
117 }
118
119 /**
120 * Create a clone of this node, a shallow copy
121 * @return {RangeNode}
122 */
123 RangeNode.prototype.clone = function () {
124 return new RangeNode(this.start, this.end, this.step && this.step)
125 }
126
127 /**
128 * Calculate the necessary parentheses
129 * @param {Node} node
130 * @param {string} parenthesis
131 * @return {Object} parentheses
132 * @private
133 */
134 function calculateNecessaryParentheses (node, parenthesis) {
135 const precedence = getPrecedence(node, parenthesis)
136 const parens = {}
137
138 const startPrecedence = getPrecedence(node.start, parenthesis)
139 parens.start = ((startPrecedence !== null) && (startPrecedence <= precedence)) ||
140 (parenthesis === 'all')
141
142 if (node.step) {
143 const stepPrecedence = getPrecedence(node.step, parenthesis)
144 parens.step = ((stepPrecedence !== null) && (stepPrecedence <= precedence)) ||
145 (parenthesis === 'all')
146 }
147
148 const endPrecedence = getPrecedence(node.end, parenthesis)
149 parens.end = ((endPrecedence !== null) && (endPrecedence <= precedence)) ||
150 (parenthesis === 'all')
151
152 return parens
153 }
154
155 /**
156 * Get string representation
157 * @param {Object} options
158 * @return {string} str
159 */
160 RangeNode.prototype._toString = function (options) {
161 const parenthesis = (options && options.parenthesis) ? options.parenthesis : 'keep'
162 const parens = calculateNecessaryParentheses(this, parenthesis)
163
164 // format string as start:step:stop
165 let str
166
167 let start = this.start.toString(options)
168 if (parens.start) {
169 start = '(' + start + ')'
170 }
171 str = start
172
173 if (this.step) {
174 let step = this.step.toString(options)
175 if (parens.step) {
176 step = '(' + step + ')'
177 }
178 str += ':' + step
179 }
180
181 let end = this.end.toString(options)
182 if (parens.end) {
183 end = '(' + end + ')'
184 }
185 str += ':' + end
186
187 return str
188 }
189
190 /**
191 * Get a JSON representation of the node
192 * @returns {Object}
193 */
194 RangeNode.prototype.toJSON = function () {
195 return {
196 mathjs: 'RangeNode',
197 start: this.start,
198 end: this.end,
199 step: this.step
200 }
201 }
202
203 /**
204 * Instantiate an RangeNode from its JSON representation
205 * @param {Object} json An object structured like
206 * `{"mathjs": "RangeNode", "start": ..., "end": ..., "step": ...}`,
207 * where mathjs is optional
208 * @returns {RangeNode}
209 */
210 RangeNode.fromJSON = function (json) {
211 return new RangeNode(json.start, json.end, json.step)
212 }
213
214 /**
215 * Get HTML representation
216 * @param {Object} options
217 * @return {string} str
218 */
219 RangeNode.prototype.toHTML = function (options) {
220 const parenthesis = (options && options.parenthesis) ? options.parenthesis : 'keep'
221 const parens = calculateNecessaryParentheses(this, parenthesis)
222
223 // format string as start:step:stop
224 let str
225
226 let start = this.start.toHTML(options)
227 if (parens.start) {
228 start = '<span class="math-parenthesis math-round-parenthesis">(</span>' + start + '<span class="math-parenthesis math-round-parenthesis">)</span>'
229 }
230 str = start
231
232 if (this.step) {
233 let step = this.step.toHTML(options)
234 if (parens.step) {
235 step = '<span class="math-parenthesis math-round-parenthesis">(</span>' + step + '<span class="math-parenthesis math-round-parenthesis">)</span>'
236 }
237 str += '<span class="math-operator math-range-operator">:</span>' + step
238 }
239
240 let end = this.end.toHTML(options)
241 if (parens.end) {
242 end = '<span class="math-parenthesis math-round-parenthesis">(</span>' + end + '<span class="math-parenthesis math-round-parenthesis">)</span>'
243 }
244 str += '<span class="math-operator math-range-operator">:</span>' + end
245
246 return str
247 }
248
249 /**
250 * Get LaTeX representation
251 * @params {Object} options
252 * @return {string} str
253 */
254 RangeNode.prototype._toTex = function (options) {
255 const parenthesis = (options && options.parenthesis) ? options.parenthesis : 'keep'
256 const parens = calculateNecessaryParentheses(this, parenthesis)
257
258 let str = this.start.toTex(options)
259 if (parens.start) {
260 str = `\\left(${str}\\right)`
261 }
262
263 if (this.step) {
264 let step = this.step.toTex(options)
265 if (parens.step) {
266 step = `\\left(${step}\\right)`
267 }
268 str += ':' + step
269 }
270
271 let end = this.end.toTex(options)
272 if (parens.end) {
273 end = `\\left(${end}\\right)`
274 }
275 str += ':' + end
276
277 return str
278 }
279
280 return RangeNode
281}, { isClass: true, isNode: true })