UNPKG

6.6 kBJavaScriptView Raw
1'use strict'
2
3const stringify = require('../../utils/string').stringify
4const escape = require('../../utils/string').escape
5const isSafeProperty = require('../../utils/customs').isSafeProperty
6const hasOwnProperty = require('../../utils/object').hasOwnProperty
7
8function factory (type, config, load, typed) {
9 const Node = load(require('./Node'))
10
11 /**
12 * @constructor ObjectNode
13 * @extends {Node}
14 * Holds an object with keys/values
15 * @param {Object.<string, Node>} [properties] object with key/value pairs
16 */
17 function ObjectNode (properties) {
18 if (!(this instanceof ObjectNode)) {
19 throw new SyntaxError('Constructor must be called with the new operator')
20 }
21
22 this.properties = properties || {}
23
24 // validate input
25 if (properties) {
26 if (!(typeof properties === 'object') || !Object.keys(properties).every(function (key) {
27 return type.isNode(properties[key])
28 })) {
29 throw new TypeError('Object containing Nodes expected')
30 }
31 }
32 }
33
34 ObjectNode.prototype = new Node()
35
36 ObjectNode.prototype.type = 'ObjectNode'
37
38 ObjectNode.prototype.isObjectNode = true
39
40 /**
41 * Compile a node into a JavaScript function.
42 * This basically pre-calculates as much as possible and only leaves open
43 * calculations which depend on a dynamic scope with variables.
44 * @param {Object} math Math.js namespace with functions and constants.
45 * @param {Object} argNames An object with argument names as key and `true`
46 * as value. Used in the SymbolNode to optimize
47 * for arguments from user assigned functions
48 * (see FunctionAssignmentNode) or special symbols
49 * like `end` (see IndexNode).
50 * @return {function} Returns a function which can be called like:
51 * evalNode(scope: Object, args: Object, context: *)
52 */
53 ObjectNode.prototype._compile = function (math, argNames) {
54 const evalEntries = {}
55
56 for (const key in this.properties) {
57 if (hasOwnProperty(this.properties, key)) {
58 // we stringify/parse the key here to resolve unicode characters,
59 // so you cannot create a key like {"co\\u006Estructor": null}
60 const stringifiedKey = stringify(key)
61 const parsedKey = JSON.parse(stringifiedKey)
62 if (!isSafeProperty(this.properties, parsedKey)) {
63 throw new Error('No access to property "' + parsedKey + '"')
64 }
65
66 evalEntries[parsedKey] = this.properties[key]._compile(math, argNames)
67 }
68 }
69
70 return function evalObjectNode (scope, args, context) {
71 const obj = {}
72
73 for (const key in evalEntries) {
74 if (hasOwnProperty(evalEntries, key)) {
75 obj[key] = evalEntries[key](scope, args, context)
76 }
77 }
78
79 return obj
80 }
81 }
82
83 /**
84 * Execute a callback for each of the child nodes of this node
85 * @param {function(child: Node, path: string, parent: Node)} callback
86 */
87 ObjectNode.prototype.forEach = function (callback) {
88 for (const key in this.properties) {
89 if (this.properties.hasOwnProperty(key)) {
90 callback(this.properties[key], 'properties[' + stringify(key) + ']', this)
91 }
92 }
93 }
94
95 /**
96 * Create a new ObjectNode having it's childs be the results of calling
97 * the provided callback function for each of the childs of the original node.
98 * @param {function(child: Node, path: string, parent: Node): Node} callback
99 * @returns {ObjectNode} Returns a transformed copy of the node
100 */
101 ObjectNode.prototype.map = function (callback) {
102 const properties = {}
103 for (const key in this.properties) {
104 if (this.properties.hasOwnProperty(key)) {
105 properties[key] = this._ifNode(callback(this.properties[key],
106 'properties[' + stringify(key) + ']', this))
107 }
108 }
109 return new ObjectNode(properties)
110 }
111
112 /**
113 * Create a clone of this node, a shallow copy
114 * @return {ObjectNode}
115 */
116 ObjectNode.prototype.clone = function () {
117 const properties = {}
118 for (const key in this.properties) {
119 if (this.properties.hasOwnProperty(key)) {
120 properties[key] = this.properties[key]
121 }
122 }
123 return new ObjectNode(properties)
124 }
125
126 /**
127 * Get string representation
128 * @param {Object} options
129 * @return {string} str
130 * @override
131 */
132 ObjectNode.prototype._toString = function (options) {
133 const entries = []
134 for (const key in this.properties) {
135 if (this.properties.hasOwnProperty(key)) {
136 entries.push(stringify(key) + ': ' + this.properties[key].toString(options))
137 }
138 }
139 return '{' + entries.join(', ') + '}'
140 }
141
142 /**
143 * Get a JSON representation of the node
144 * @returns {Object}
145 */
146 ObjectNode.prototype.toJSON = function () {
147 return {
148 mathjs: 'ObjectNode',
149 properties: this.properties
150 }
151 }
152
153 /**
154 * Instantiate an OperatorNode from its JSON representation
155 * @param {Object} json An object structured like
156 * `{"mathjs": "ObjectNode", "properties": {...}}`,
157 * where mathjs is optional
158 * @returns {ObjectNode}
159 */
160 ObjectNode.fromJSON = function (json) {
161 return new ObjectNode(json.properties)
162 }
163
164 /**
165 * Get HTML representation
166 * @param {Object} options
167 * @return {string} str
168 * @override
169 */
170 ObjectNode.prototype.toHTML = function (options) {
171 const entries = []
172 for (const key in this.properties) {
173 if (this.properties.hasOwnProperty(key)) {
174 entries.push('<span class="math-symbol math-property">' + escape(key) + '</span>' + '<span class="math-operator math-assignment-operator math-property-assignment-operator math-binary-operator">:</span>' + this.properties[key].toHTML(options))
175 }
176 }
177 return '<span class="math-parenthesis math-curly-parenthesis">{</span>' + entries.join('<span class="math-separator">,</span>') + '<span class="math-parenthesis math-curly-parenthesis">}</span>'
178 }
179
180 /**
181 * Get LaTeX representation
182 * @param {Object} options
183 * @return {string} str
184 */
185 ObjectNode.prototype._toTex = function (options) {
186 const entries = []
187 for (const key in this.properties) {
188 if (this.properties.hasOwnProperty(key)) {
189 entries.push('\\mathbf{' + key + ':} & ' + this.properties[key].toTex(options) + '\\\\')
190 }
191 }
192 return `\\left\\{\\begin{array}{ll}${entries.join('\n')}\\end{array}\\right\\}`
193 }
194
195 return ObjectNode
196}
197
198exports.name = 'ObjectNode'
199exports.path = 'expression.node'
200exports.factory = factory