mirror of
https://github.com/josdejong/mathjs.git
synced 2025-12-08 19:46:04 +00:00
267 lines
7.1 KiB
JavaScript
267 lines
7.1 KiB
JavaScript
var number= require('../../util/number'),
|
|
|
|
Node = require('./Node'),
|
|
RangeNode = require('./RangeNode'),
|
|
SymbolNode = require('./SymbolNode'),
|
|
|
|
BigNumber = require('bignumber.js'),
|
|
Index = require('../../type/Index'),
|
|
Range = require('../../type/Range'),
|
|
|
|
isNumber = number.isNumber,
|
|
toNumber = number.toNumber;
|
|
|
|
/**
|
|
* @constructor UpdateNode
|
|
* Update a symbol value, like a(2,3) = 4.5
|
|
*
|
|
* @param {Object} math The math namespace containing all functions
|
|
* @param {String} name Symbol name
|
|
* @param {Node[] | undefined} params One or more parameters
|
|
* @param {Scope[]} paramScopes A scope for every parameter, where the
|
|
* index variable 'end' can be defined.
|
|
* @param {Node} expr The expression defining the symbol
|
|
* @param {Scope} scope Scope to store the result
|
|
*/
|
|
function UpdateNode(math, name, params, paramScopes, expr, scope) {
|
|
this.math = math;
|
|
|
|
this.name = name;
|
|
this.params = params;
|
|
this.paramScopes = paramScopes;
|
|
this.expr = expr;
|
|
this.scope = scope;
|
|
|
|
// TODO: remove checking for contextParams here
|
|
// check whether any of the params expressions uses the context symbol 'end'
|
|
this.hasContextParams = false;
|
|
var filter = {
|
|
type: SymbolNode,
|
|
properties: {
|
|
name: 'end'
|
|
}
|
|
};
|
|
|
|
for (var i = 0, len = params.length; i < len; i++) {
|
|
if (params[i].find(filter).length > 0) {
|
|
this.hasContextParams = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
UpdateNode.prototype = new Node();
|
|
|
|
/**
|
|
* Evaluate the assignment
|
|
* @return {*} result
|
|
*/
|
|
UpdateNode.prototype.eval = function() {
|
|
if (this.expr === undefined) {
|
|
throw new Error('Undefined symbol ' + this.name);
|
|
}
|
|
|
|
var result;
|
|
|
|
// test if definition is currently undefined
|
|
var prevResult = this.scope.get(this.name);
|
|
if (prevResult == undefined) {
|
|
throw new Error('Undefined symbol ' + this.name);
|
|
}
|
|
|
|
// evaluate the values of context parameter 'end' when needed
|
|
if (this.hasContextParams && typeof prevResult !== 'function') {
|
|
var paramScopes = this.paramScopes,
|
|
size = this.math.size(prevResult).valueOf();
|
|
|
|
if (paramScopes && size) {
|
|
for (var i = 0, len = this.params.length; i < len; i++) {
|
|
var paramScope = paramScopes[i];
|
|
if (paramScope) {
|
|
paramScope.set('end', size[i]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// change part of a matrix, for example "a=[]", "a(2,3)=4.5"
|
|
var paramResults = [];
|
|
this.params.forEach(function (param) {
|
|
var result;
|
|
|
|
if (param instanceof RangeNode) {
|
|
result = param.toRange();
|
|
}
|
|
else {
|
|
result = param.eval();
|
|
}
|
|
|
|
// convert big number to number
|
|
if (result instanceof BigNumber) result = toNumber(result);
|
|
|
|
// TODO: implement support for BigNumber
|
|
|
|
// change from one-based to zero-based range
|
|
if (result instanceof Range) {
|
|
result.start --;
|
|
result.end --;
|
|
}
|
|
else if (isNumber(result)) {
|
|
// number
|
|
result--;
|
|
}
|
|
else {
|
|
throw new TypeError('Number or Range expected');
|
|
}
|
|
|
|
paramResults.push(result);
|
|
});
|
|
|
|
// evaluate the expression
|
|
var exprResult = this.expr.eval();
|
|
|
|
// replace subset
|
|
var index = Index.create(paramResults);
|
|
result = this.math.subset(prevResult, index, exprResult);
|
|
|
|
this.scope.set(this.name, result);
|
|
|
|
return result;
|
|
};
|
|
|
|
/**
|
|
* Compile the node to javascript code
|
|
* @param {Object} math math.js instance
|
|
* @return {String} js
|
|
* @private
|
|
*/
|
|
UpdateNode.prototype._compile = function (math) {
|
|
var name = new SymbolNode(this.name);
|
|
|
|
// check whether any of the params expressions uses the context symbol 'end'
|
|
var filter = {
|
|
type: SymbolNode,
|
|
properties: {
|
|
name: 'end'
|
|
}
|
|
};
|
|
var paramsUseEnd = this.params.map(function (param) {
|
|
return param.find(filter).length > 0;
|
|
});
|
|
var useEnd = paramsUseEnd.some(function (use) {
|
|
return use;
|
|
});
|
|
|
|
// TODO: implement support for bignumber (currently bignumbers are silently
|
|
// reduced to numbers when changing the value to zero-based)
|
|
|
|
var ranges = this.params.map(function(param, i) {
|
|
// TODO: implement an IndexNode
|
|
var useEnd = paramsUseEnd[i];
|
|
if (param instanceof RangeNode) {
|
|
if (useEnd) {
|
|
// resolve end and create range (change from one based to zero based)
|
|
return '(function (scope) {' +
|
|
' scope = Object.create(scope); ' +
|
|
' scope["end"] = size[' + i + '];' +
|
|
' var step = ' + (param.step ? param.step._compile(math) : '1') + ';' +
|
|
' return [' +
|
|
' ' + param.start._compile(math) + ' - 1, ' +
|
|
' ' + param.end._compile(math) + ' - (step > 0 ? 0 : 2), ' +
|
|
' step' +
|
|
' ];' +
|
|
'})(scope)';
|
|
}
|
|
else {
|
|
// create range (change from one based to zero based)
|
|
return '(function () {' +
|
|
' var step = ' + (param.step ? param.step._compile(math) : '1') + ';' +
|
|
' return [' +
|
|
' ' + param.start._compile(math) + ' - 1, ' +
|
|
' ' + param.end._compile(math) + ' - (step > 0 ? 0 : 2), ' +
|
|
' step' +
|
|
' ];' +
|
|
'})()';
|
|
}
|
|
}
|
|
else {
|
|
if (useEnd) {
|
|
// resolve the parameter 'end', adjust the index value to zero-based
|
|
return '(function (scope) {' +
|
|
' scope = Object.create(scope); ' +
|
|
' scope["end"] = size[' + i + '];' +
|
|
' return ' + param._compile(math) + ' - 1;' +
|
|
'})(scope)'
|
|
}
|
|
else {
|
|
// just evaluate the expression, and change from one-based to zero-based
|
|
return param._compile(math) + ' - 1';
|
|
}
|
|
}
|
|
});
|
|
|
|
var code = 'scope["' + name + '\"] = ' +
|
|
' math.subset(' + name._compile(math) + ', ' +
|
|
' math.index(' + ranges.join(', ') + '), ' +
|
|
' ' + this.expr._compile(math) + ')';
|
|
|
|
if (paramsUseEnd) {
|
|
// if any of the parameters uses the 'end' symbol, calculate the size of
|
|
// the matrix, so 'end' can be resolved
|
|
code = '(function () {' +
|
|
' var size = math.size(' + name._compile(math) + ').valueOf();' +
|
|
' return ' + code + ';' +
|
|
'})()'
|
|
}
|
|
|
|
return code;
|
|
};
|
|
|
|
/**
|
|
* Find all nodes matching given filter
|
|
* @param {Object} filter See Node.find for a description of the filter settings
|
|
* @returns {Node[]} nodes
|
|
*/
|
|
UpdateNode.prototype.find = function (filter) {
|
|
var nodes = [];
|
|
|
|
// check itself
|
|
if (this.match(filter)) {
|
|
nodes.push(this);
|
|
}
|
|
|
|
// search in parameters
|
|
var params = this.params;
|
|
if (params) {
|
|
for (var i = 0, len = params.length; i < len; i++) {
|
|
nodes = nodes.concat(params[i].find(filter));
|
|
}
|
|
}
|
|
|
|
// search in expression
|
|
if (this.expr) {
|
|
nodes = nodes.concat(this.expr.find(filter));
|
|
}
|
|
|
|
return nodes;
|
|
};
|
|
|
|
/**
|
|
* Get string representation
|
|
* @return {String}
|
|
*/
|
|
UpdateNode.prototype.toString = function() {
|
|
var str = '';
|
|
|
|
str += this.name;
|
|
if (this.params && this.params.length) {
|
|
str += '(' + this.params.join(', ') + ')';
|
|
}
|
|
str += ' = ';
|
|
str += this.expr.toString();
|
|
|
|
return str;
|
|
};
|
|
|
|
module.exports = UpdateNode;
|