mathjs/lib/expression/node/AccessorNode.js

231 lines
7.5 KiB
JavaScript

'use strict';
var stringify = require('../../utils/string').stringify;
var getSafeProperty = require('../../utils/customs').getSafeProperty;
function factory (type, config, load, typed) {
var register = load(require('./compile')).register;
var compile = load(require('./compile')).compile;
var Node = load(require('./Node'));
var IndexNode = load(require('./IndexNode'));
var access = load(require('./utils/access'));
/**
* @constructor AccessorNode
* @extends {Node}
* Access an object property or get a matrix subset
*
* @param {Node} object The object from which to retrieve
* a property or subset.
* @param {IndexNode} index IndexNode containing ranges
*/
function AccessorNode(object, index) {
if (!(this instanceof AccessorNode)) {
throw new SyntaxError('Constructor must be called with the new operator');
}
if (!type.isNode(object)) {
throw new TypeError('Node expected for parameter "object"');
}
if (!type.isIndexNode(index)) {
throw new TypeError('IndexNode expected for parameter "index"');
}
this.object = object || null;
this.index = index;
// readonly property name
Object.defineProperty(this, 'name', {
get: function () {
if (this.index) {
return (this.index.isObjectProperty())
? this.index.getObjectProperty()
: '';
}
else {
return this.object.name || '';
}
}.bind(this),
set: function () {
throw new Error('Cannot assign a new name, name is read-only');
}
});
}
AccessorNode.prototype = new Node();
AccessorNode.prototype.type = 'AccessorNode';
AccessorNode.prototype.isAccessorNode = true;
/**
* Compile the node to javascript code
* @param {AccessorNode} node Node to be compiled
* @param {Object} defs Object which can be used to define functions
* or constants globally available for the compiled
* expression
* @param {Object} args Object with local function arguments, the key is
* the name of the argument, and the value is `true`.
* The object may not be mutated, but must be
* extended instead.
* @return {string} js
* @private
*/
function compileAccessorNode(node, defs, args) {
if (!(node instanceof AccessorNode)) {
throw new TypeError('No valid AccessorNode')
}
defs.access = access;
defs.getSafeProperty = getSafeProperty;
var object = compile(node.object, defs, args);
var index = compile(node.index, defs, args);
if (node.index.isObjectProperty()) {
var jsProp = stringify(node.index.getObjectProperty());
return 'getSafeProperty(' + object + ', ' + jsProp + ')';
}
else if (node.index.needsSize()) {
// if some parameters use the 'end' parameter, we need to calculate the size
return '(function () {' +
' var object = ' + object + ';' +
' var size = math.size(object).valueOf();' +
' return access(object, ' + index + ');' +
'})()';
}
else {
return 'access(' + object + ', ' + index + ')';
}
}
// register the compile function
register(AccessorNode.prototype.type, compileAccessorNode);
/**
* Compile a node into a JavaScript function.
* This basically pre-calculates as much as possible and only leaves open
* calculations which depend on a dynamic scope with variables.
* @param {Object} math Math.js namespace with functions and constants.
* @param {Object} argNames An object with argument names as key and `true`
* as value. Used in the SymbolNode to optimize
* for arguments from user assigned functions
* (see FunctionAssignmentNode) or special symbols
* like `end` (see IndexNode).
* @return {function} Returns a function which can be called like:
* evalNode(scope: Object, args: Object, context: *)
*/
AccessorNode.prototype._compile = function (math, argNames) {
var evalObject = this.object._compile(math, argNames);
var evalIndex = this.index._compile(math, argNames);
if (this.index.isObjectProperty()) {
var prop = this.index.getObjectProperty();
return function evalAccessorNode(scope, args, context) {
return getSafeProperty(evalObject(scope, args, context), prop);
};
}
else {
return function evalAccessorNode (scope, args, context) {
var object = evalObject(scope, args, context);
var index = evalIndex(scope, args, object); // we pass object here instead of context
return access(object, index);
};
}
}
/**
* Execute a callback for each of the child nodes of this node
* @param {function(child: Node, path: string, parent: Node)} callback
*/
AccessorNode.prototype.forEach = function (callback) {
callback(this.object, 'object', this);
callback(this.index, 'index', this);
};
/**
* Create a new AccessorNode having it's childs be the results of calling
* the provided callback function for each of the childs of the original node.
* @param {function(child: Node, path: string, parent: Node): Node} callback
* @returns {AccessorNode} Returns a transformed copy of the node
*/
AccessorNode.prototype.map = function (callback) {
return new AccessorNode(
this._ifNode(callback(this.object, 'object', this)),
this._ifNode(callback(this.index, 'index', this))
);
};
/**
* Create a clone of this node, a shallow copy
* @return {AccessorNode}
*/
AccessorNode.prototype.clone = function () {
return new AccessorNode(this.object, this.index);
};
/**
* Get string representation
* @param {Object} options
* @return {string}
*/
AccessorNode.prototype._toString = function (options) {
var object = this.object.toString(options);
if (needParenthesis(this.object)) {
object = '(' + object + ')';
}
return object + this.index.toString(options);
};
/**
* Get HTML representation
* @param {Object} options
* @return {string}
*/
AccessorNode.prototype.toHTML = function (options) {
var object = this.object.toHTML(options);
if (needParenthesis(this.object)) {
object = '<span class="math-parenthesis math-round-parenthesis">(</span>' + object + '<span class="math-parenthesis math-round-parenthesis">)</span>';
}
return object + this.index.toHTML(options);
};
/**
* Get LaTeX representation
* @param {Object} options
* @return {string}
*/
AccessorNode.prototype._toTex = function (options) {
var object = this.object.toTex(options);
if (needParenthesis(this.object)) {
object = '\\left(' + object + '\\right)';
}
return object + this.index.toTex(options);
};
/**
* Are parenthesis needed?
* @private
*/
function needParenthesis(node) {
// TODO: maybe make a method on the nodes which tells whether they need parenthesis?
return !(
type.isAccessorNode(node) ||
type.isArrayNode(node) ||
type.isConstantNode(node) ||
type.isFunctionNode(node) ||
type.isObjectNode(node) ||
type.isParenthesisNode(node) ||
type.isSymbolNode(node));
}
return AccessorNode;
}
exports.name = 'AccessorNode';
exports.path = 'expression.node';
exports.factory = factory;