UNPKG

6.38 kBJavaScriptView Raw
1'use strict'
2
3const getSafeProperty = require('../../utils/customs').getSafeProperty
4
5function factory (type, config, load, typed) {
6 const Node = load(require('./Node'))
7 const access = load(require('./utils/access'))
8
9 /**
10 * @constructor AccessorNode
11 * @extends {Node}
12 * Access an object property or get a matrix subset
13 *
14 * @param {Node} object The object from which to retrieve
15 * a property or subset.
16 * @param {IndexNode} index IndexNode containing ranges
17 */
18 function AccessorNode (object, index) {
19 if (!(this instanceof AccessorNode)) {
20 throw new SyntaxError('Constructor must be called with the new operator')
21 }
22
23 if (!type.isNode(object)) {
24 throw new TypeError('Node expected for parameter "object"')
25 }
26 if (!type.isIndexNode(index)) {
27 throw new TypeError('IndexNode expected for parameter "index"')
28 }
29
30 this.object = object || null
31 this.index = index
32
33 // readonly property name
34 Object.defineProperty(this, 'name', {
35 get: function () {
36 if (this.index) {
37 return (this.index.isObjectProperty())
38 ? this.index.getObjectProperty()
39 : ''
40 } else {
41 return this.object.name || ''
42 }
43 }.bind(this),
44 set: function () {
45 throw new Error('Cannot assign a new name, name is read-only')
46 }
47 })
48 }
49
50 AccessorNode.prototype = new Node()
51
52 AccessorNode.prototype.type = 'AccessorNode'
53
54 AccessorNode.prototype.isAccessorNode = true
55
56 /**
57 * Compile a node into a JavaScript function.
58 * This basically pre-calculates as much as possible and only leaves open
59 * calculations which depend on a dynamic scope with variables.
60 * @param {Object} math Math.js namespace with functions and constants.
61 * @param {Object} argNames An object with argument names as key and `true`
62 * as value. Used in the SymbolNode to optimize
63 * for arguments from user assigned functions
64 * (see FunctionAssignmentNode) or special symbols
65 * like `end` (see IndexNode).
66 * @return {function} Returns a function which can be called like:
67 * evalNode(scope: Object, args: Object, context: *)
68 */
69 AccessorNode.prototype._compile = function (math, argNames) {
70 const evalObject = this.object._compile(math, argNames)
71 const evalIndex = this.index._compile(math, argNames)
72
73 if (this.index.isObjectProperty()) {
74 const prop = this.index.getObjectProperty()
75 return function evalAccessorNode (scope, args, context) {
76 return getSafeProperty(evalObject(scope, args, context), prop)
77 }
78 } else {
79 return function evalAccessorNode (scope, args, context) {
80 const object = evalObject(scope, args, context)
81 const index = evalIndex(scope, args, object) // we pass object here instead of context
82 return access(object, index)
83 }
84 }
85 }
86
87 /**
88 * Execute a callback for each of the child nodes of this node
89 * @param {function(child: Node, path: string, parent: Node)} callback
90 */
91 AccessorNode.prototype.forEach = function (callback) {
92 callback(this.object, 'object', this)
93 callback(this.index, 'index', this)
94 }
95
96 /**
97 * Create a new AccessorNode having it's childs be the results of calling
98 * the provided callback function for each of the childs of the original node.
99 * @param {function(child: Node, path: string, parent: Node): Node} callback
100 * @returns {AccessorNode} Returns a transformed copy of the node
101 */
102 AccessorNode.prototype.map = function (callback) {
103 return new AccessorNode(
104 this._ifNode(callback(this.object, 'object', this)),
105 this._ifNode(callback(this.index, 'index', this))
106 )
107 }
108
109 /**
110 * Create a clone of this node, a shallow copy
111 * @return {AccessorNode}
112 */
113 AccessorNode.prototype.clone = function () {
114 return new AccessorNode(this.object, this.index)
115 }
116
117 /**
118 * Get string representation
119 * @param {Object} options
120 * @return {string}
121 */
122 AccessorNode.prototype._toString = function (options) {
123 let object = this.object.toString(options)
124 if (needParenthesis(this.object)) {
125 object = '(' + object + ')'
126 }
127
128 return object + this.index.toString(options)
129 }
130
131 /**
132 * Get HTML representation
133 * @param {Object} options
134 * @return {string}
135 */
136 AccessorNode.prototype.toHTML = function (options) {
137 let object = this.object.toHTML(options)
138 if (needParenthesis(this.object)) {
139 object = '<span class="math-parenthesis math-round-parenthesis">(</span>' + object + '<span class="math-parenthesis math-round-parenthesis">)</span>'
140 }
141
142 return object + this.index.toHTML(options)
143 }
144
145 /**
146 * Get LaTeX representation
147 * @param {Object} options
148 * @return {string}
149 */
150 AccessorNode.prototype._toTex = function (options) {
151 let object = this.object.toTex(options)
152 if (needParenthesis(this.object)) {
153 object = `\\left(' + object + '\\right)`
154 }
155
156 return object + this.index.toTex(options)
157 }
158
159 /**
160 * Get a JSON representation of the node
161 * @returns {Object}
162 */
163 AccessorNode.prototype.toJSON = function () {
164 return {
165 mathjs: 'AccessorNode',
166 object: this.object,
167 index: this.index
168 }
169 }
170
171 /**
172 * Instantiate an AccessorNode from its JSON representation
173 * @param {Object} json An object structured like
174 * `{"mathjs": "AccessorNode", object: ..., index: ...}`,
175 * where mathjs is optional
176 * @returns {AccessorNode}
177 */
178 AccessorNode.fromJSON = function (json) {
179 return new AccessorNode(json.object, json.index)
180 }
181
182 /**
183 * Are parenthesis needed?
184 * @private
185 */
186 function needParenthesis (node) {
187 // TODO: maybe make a method on the nodes which tells whether they need parenthesis?
188 return !(
189 type.isAccessorNode(node) ||
190 type.isArrayNode(node) ||
191 type.isConstantNode(node) ||
192 type.isFunctionNode(node) ||
193 type.isObjectNode(node) ||
194 type.isParenthesisNode(node) ||
195 type.isSymbolNode(node))
196 }
197
198 return AccessorNode
199}
200
201exports.name = 'AccessorNode'
202exports.path = 'expression.node'
203exports.factory = factory