From 924299c42b4d42ce09c35795cc5c8a8d1b287585 Mon Sep 17 00:00:00 2001 From: jos Date: Tue, 23 Jan 2018 12:08:29 +0100 Subject: [PATCH 1/6] Changed `ConstantNode(valueStr, valueType`) to `ConstantNode(value)` (breaking change) --- HISTORY.md | 5 + docs/expressions/expression_trees.md | 5 +- lib/expression/node/ConstantNode.js | 157 +++--------------- lib/expression/node/IndexNode.js | 2 +- lib/expression/parse.js | 36 +++- lib/function/algebra/derivative.js | 132 ++++++++------- lib/function/algebra/simplify.js | 5 +- .../algebra/simplify/simplifyConstant.js | 3 +- lib/function/algebra/simplify/simplifyCore.js | 61 ++++--- .../algebra/utils/createNumericValue.js | 31 ++++ test/expression/node/AccessorNode.test.js | 4 +- test/expression/node/ArrayNode.test.js | 4 +- test/expression/node/AssignmentNode.test.js | 12 +- test/expression/node/BlockNode.test.js | 4 +- test/expression/node/ConditionalNode.test.js | 4 +- test/expression/node/ConstantNode.test.js | 111 +++++-------- .../node/FunctionAssignmentNode.test.js | 4 +- test/expression/node/FunctionNode.test.js | 4 +- test/expression/node/IndexNode.test.js | 8 +- test/expression/node/ObjectNode.test.js | 4 +- test/expression/node/OperatorNode.test.js | 8 +- test/expression/node/RangeNode.test.js | 4 +- test/expression/security.test.js | 2 +- 23 files changed, 278 insertions(+), 332 deletions(-) create mode 100644 lib/function/algebra/utils/createNumericValue.js diff --git a/HISTORY.md b/HISTORY.md index a527d0a99..c0039fe80 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -11,6 +11,11 @@ Breaking changes: - Internal code is easier to understand, maintain, and debug. Breaking change here: When using custom nodes in the expression parser, the syntax of `_compile` has changed. This is an undocumented feature though. +- The class `ConstantNode` is changed such that it just holds a value + instead of holding a stringified value and it's type. + `ConstantNode(valueStr, valueType`) is now `ConstantNode(value)` + Stringification uses `math.format`, which may result in differently + formatted numeric output. ## 2018-01-17, version 3.20.1 diff --git a/docs/expressions/expression_trees.md b/docs/expressions/expression_trees.md index 4ccf773c9..2ef7db7f5 100644 --- a/docs/expressions/expression_trees.md +++ b/docs/expressions/expression_trees.md @@ -405,13 +405,12 @@ var node2 = new math.expression.node.ConditionalNode(condition, trueExpr, fa Construction: ``` -new ConstantNode(value: * [, valueType: string]) +new ConstantNode(value: *) ``` Properties: - `value: *` -- `valueType: string` Examples: @@ -419,7 +418,7 @@ Examples: var node1 = math.parse('2.4'); var node2 = new math.expression.node.ConstantNode(2.4); -var node3 = new math.expression.node.ConstantNode('2.4', 'number'); +var node3 = new math.expression.node.ConstantNode('foo'); ``` diff --git a/lib/expression/node/ConstantNode.js b/lib/expression/node/ConstantNode.js index 9337479cb..dbcaefb4e 100644 --- a/lib/expression/node/ConstantNode.js +++ b/lib/expression/node/ConstantNode.js @@ -1,79 +1,35 @@ 'use strict'; var getType = require('../../utils/types').type; -var stringify = require('../../utils/string').stringify; -var escape = require('../../utils/string').escape; +var format = require('../../utils/string').format; function factory (type, config, load, typed) { var Node = load(require('./Node')); /** - * A ConstantNode holds a constant value like a number or string. A ConstantNode - * stores a stringified version of the value and uses this to compile to - * JavaScript. - * - * In case of a stringified number as input, this may be compiled to a BigNumber - * when the math instance is configured for BigNumbers. + * A ConstantNode holds a constant value like a number or string. * * Usage: * - * // stringified values with type - * new ConstantNode('2.3', 'number'); - * new ConstantNode('true', 'boolean'); - * new ConstantNode('hello', 'string'); - * - * // non-stringified values, type will be automatically detected * new ConstantNode(2.3); * new ConstantNode('hello'); * - * @param {string | number | boolean | null | undefined} value - * When valueType is provided, value must contain - * an uninterpreted string representing the value. - * When valueType is undefined, value can be a - * number, string, boolean, null, or undefined, and - * the type will be determined automatically. - * @param {string} [valueType] The type of value. Choose from 'number', 'string', - * 'boolean', 'undefined', 'null' + * @param {*} value Value can be any type (number, BigNumber, string, ...) * @constructor ConstantNode * @extends {Node} */ - function ConstantNode(value, valueType) { - // TODO: make the whole valueType redundant, simply parse numbers in parse.js already - + function ConstantNode(value) { if (!(this instanceof ConstantNode)) { throw new SyntaxError('Constructor must be called with the new operator'); } - if (valueType) { - if (typeof valueType !== 'string') { - throw new TypeError('String expected for parameter "valueType"'); - } - if (typeof value !== 'string') { - throw new TypeError('String expected for parameter "value"'); - } - - this.value = value; - this.valueType = valueType; - } - else { - // stringify the value and determine the type - this.value = value + ''; - this.valueType = getType(value); + if (arguments.length === 2) { + throw new SyntaxError('new ConstantNode(valueStr, valueType) is not supported anymore since math v4.0.0. Use new ConstantNode(value) instead, where value is a non-stringified value.'); } - if (!SUPPORTED_TYPES[this.valueType]) { - throw new TypeError('Unsupported type of value "' + this.valueType + '"'); - } + this.value = value; } - var SUPPORTED_TYPES = { - 'number': true, - 'string': true, - 'boolean': true, - 'undefined': true, - 'null': true - }; - ConstantNode.prototype = new Node(); ConstantNode.prototype.type = 'ConstantNode'; @@ -94,74 +50,13 @@ function factory (type, config, load, typed) { * evalNode(scope: Object, args: Object, context: *) */ ConstantNode.prototype._compile = function (math, argNames) { - var value; + var value = this.value; - switch (this.valueType) { - case 'number': - if (config.number === 'BigNumber') { - value = new type.BigNumber(this.value); - return function evalConstantNode() { - return value; - } - } - else if (config.number === 'Fraction') { - value = new type.Fraction(this.value); - return function evalConstantNode() { - return value; - } - } - else { - // remove leading zeros like '003.2' which are not allowed by JavaScript - validateNumericValue(this.value); - value = parseFloat(this.value.replace(/^(0*)[0-9]/, function (match, zeros) { - return match.substring(zeros.length); - })); - return function evalConstantNode() { - return value; - } - } - - case 'string': - value = this.value; - return function evalConstantNode() { - return value; - }; - - case 'boolean': - // prevent invalid values - return String(this.value) === 'true' - ? function () { return true; } - : function () { return false; }; - - case 'undefined': - return function evalConstantNode() { - return undefined; - }; - - case 'null': - return function evalConstantNode() { - return null; - }; - - default: - // TODO: move this error to the constructor? - throw new TypeError('Unsupported type of constant "' + this.valueType + '"'); + return function evalConstantNode() { + return value; } } - /** - * Test whether value is a string containing a numeric value - * @param {String} value - * @return {boolean} Returns true when ok - */ - function validateNumericValue (value) { - // The following regexp is relatively permissive - if (typeof value !== 'string' || - !/^[\-+]?((\d+\.?\d*)|(\d*\.?\d+))([eE][+\-]?\d+)?$/.test(value)) { - throw new Error('Invalid numeric value "' + value + '"'); - } - } - /** * Execute a callback for each of the child nodes of this node * @param {function(child: Node, path: string, parent: Node)} callback @@ -170,7 +65,6 @@ function factory (type, config, load, typed) { // nothing to do, we don't have childs }; - /** * Create a new ConstantNode having it's childs be the results of calling * the provided callback function for each of the childs of the original node. @@ -186,7 +80,7 @@ function factory (type, config, load, typed) { * @return {ConstantNode} */ ConstantNode.prototype.clone = function () { - return new ConstantNode(this.value, this.valueType); + return new ConstantNode(this.value); }; /** @@ -195,13 +89,7 @@ function factory (type, config, load, typed) { * @return {string} str */ ConstantNode.prototype._toString = function (options) { - switch (this.valueType) { - case 'string': - return stringify(this.value); - - default: - return this.value; - } + return format (this.value, options); }; /** @@ -210,9 +98,12 @@ function factory (type, config, load, typed) { * @return {string} str */ ConstantNode.prototype.toHTML = function (options) { - var value = escape(this.value); - switch (this.valueType) { - case 'number': + var value = this._toString(options); + + switch (getType(this.value)) { + case 'number': + case 'BigNumber': + case 'Fraction': return '' + value + ''; case 'string': return '' + value + ''; @@ -234,14 +125,16 @@ function factory (type, config, load, typed) { * @return {string} str */ ConstantNode.prototype._toTex = function (options) { - var value = this.value, - index; - switch (this.valueType) { + var value = this._toString(options); + + switch (getType(this.value)) { case 'string': - return '\\mathtt{' + stringify(value) + '}'; + return '\\mathtt{' + value + '}'; case 'number': - index = value.toLowerCase().indexOf('e'); + case 'BigNumber': + case 'Fraction': + var index = value.toLowerCase().indexOf('e'); if (index !== -1) { return value.substring(0, index) + '\\cdot10^{' + value.substring(index + 1) + '}'; diff --git a/lib/expression/node/IndexNode.js b/lib/expression/node/IndexNode.js index 39026d41c..25376a43c 100644 --- a/lib/expression/node/IndexNode.js +++ b/lib/expression/node/IndexNode.js @@ -188,7 +188,7 @@ function factory (type, config, load, typed) { IndexNode.prototype.isObjectProperty = function () { return this.dimensions.length === 1 && type.isConstantNode(this.dimensions[0]) && - this.dimensions[0].valueType === 'string'; + typeof this.dimensions[0].value === 'string'; }; /** diff --git a/lib/expression/parse.js b/lib/expression/parse.js index 41d64f434..fe96a08f2 100644 --- a/lib/expression/parse.js +++ b/lib/expression/parse.js @@ -19,7 +19,6 @@ function factory (type, config, load, typed) { var RangeNode = load(require('./node/RangeNode')); var SymbolNode = load(require('./node/SymbolNode')); - /** * Parse an expression. Returns a node tree, which can be evaluated by * invoking node.eval(); @@ -144,6 +143,7 @@ function factory (type, config, load, typed) { }; var extra_nodes = {}; // current extra nodes + var number_config = 'number' // 'number', 'Fraction', or 'BigNumber' var expression = ''; // current expression var comment = ''; // last parsed comment var index = 0; // current index in expr @@ -557,7 +557,7 @@ function factory (type, config, load, typed) { } else { if (!node) { - node = new ConstantNode('undefined', 'undefined'); + node = new ConstantNode(undefined); node.comment = comment; } @@ -858,7 +858,7 @@ function factory (type, config, load, typed) { if (token === ':') { // implicit start=1 (one-based) - node = new ConstantNode('1', 'number'); + node = new ConstantNode(1); } else { // explicit start @@ -1250,7 +1250,7 @@ function factory (type, config, load, typed) { str = parseStringToken(); // create constant - node = new ConstantNode(str, 'string'); + node = new ConstantNode(str); // parse index parameters node = parseAccessors(node); @@ -1439,14 +1439,36 @@ function factory (type, config, load, typed) { * @private */ function parseNumber () { - var number; + var numberStr; if (token_type === TOKENTYPE.NUMBER) { // this is a number - number = token; + numberStr = token; getToken(); - return new ConstantNode(number, 'number'); + if (config.number === 'BigNumber') { + return new ConstantNode(new type.BigNumber(numberStr)); + } + else if (config.number === 'Fraction') { + return new ConstantNode(new type.Fraction(numberStr)); + } + else { + // The following regexp is relatively permissive + if (!/^[\-+]?((\d+\.?\d*)|(\d*\.?\d+))([eE][+\-]?\d+)?$/.test(numberStr)) { + throw new Error('Invalid numeric value "' + numberStr + '"'); + } + + // remove leading zeros like '003.2' which are not allowed by JavaScript + var number = parseFloat(numberStr.replace(/^(0*)[0-9]/, function (match, zeros) { + return match.substring(zeros.length); + })); + + if (isNaN(number)) { + throw new Error('Invalid numeric value "' + numberStr + '"'); + } + + return new ConstantNode(number); + } } return parseParentheses(); diff --git a/lib/function/algebra/derivative.js b/lib/function/algebra/derivative.js index fdc61b1ba..26ca73354 100644 --- a/lib/function/algebra/derivative.js +++ b/lib/function/algebra/derivative.js @@ -3,6 +3,9 @@ function factory (type, config, load, typed) { var parse = load(require('../../expression/parse')); var simplify = load(require('./simplify')); + var equal = load(require('../relational/equal')); + var isZero = load(require('../utils/isZero')); + var createNumericValue = load(require('./utils/createNumericValue')); var ConstantNode = load(require('../../expression/node/ConstantNode')); var FunctionNode = load(require('../../expression/node/FunctionNode')); var OperatorNode = load(require('../../expression/node/OperatorNode')); @@ -142,7 +145,7 @@ function factory (type, config, load, typed) { 'Object, SymbolNode, string': function (constNodes, node, varName) { // Treat other variables like constants. For reasoning, see: // https://en.wikipedia.org/wiki/Partial_derivative - if (node.name != varName) { + if (node.name !== varName) { return constNodes[node] = true; } return false; @@ -153,14 +156,14 @@ function factory (type, config, load, typed) { }, 'Object, FunctionAssignmentNode, string': function (constNodes, node, varName) { - if (node.params.indexOf(varName) == -1) { + if (node.params.indexOf(varName) === -1) { return constNodes[node] = true; } return constTag(constNodes, node.expr, varName); }, 'Object, FunctionNode | OperatorNode, string': function (constNodes, node, varName) { - if (node.args.length != 0) { + if (node.args.length !== 0) { var isConst = constTag(constNodes, node.args[0], varName); for (var i = 1; i < node.args.length; ++i) { isConst = constTag(constNodes, node.args[i], varName) && isConst; @@ -183,14 +186,14 @@ function factory (type, config, load, typed) { */ var _derivative = typed('_derivative', { 'ConstantNode, Object': function (node) { - return new ConstantNode('0', node.valueType); + return createConstantNode(0); }, 'SymbolNode, Object': function (node, constNodes) { if (constNodes[node] !== undefined) { - return new ConstantNode('0', config.number); + return createConstantNode(0); } - return new ConstantNode('1', config.number); + return createConstantNode(1); }, 'ParenthesisNode, Object': function (node, constNodes) { @@ -199,18 +202,18 @@ function factory (type, config, load, typed) { 'FunctionAssignmentNode, Object': function (node, constNodes) { if (constNodes[node] !== undefined) { - return new ConstantNode('0', config.number); + return createConstantNode(0); } return _derivative(node.expr, constNodes); }, 'FunctionNode, Object': function (node, constNodes) { - if (node.args.length != 1) { + if (node.args.length !== 1) { funcArgsCheck(node); } if (constNodes[node] !== undefined) { - return new ConstantNode('0', config.number); + return createConstantNode(0); } var arg1 = node.args[0]; @@ -225,12 +228,12 @@ function factory (type, config, load, typed) { // d/dx(cbrt(x)) = 1 / (3x^(2/3)) div = true; funcDerivative = new OperatorNode('*', 'multiply', [ - new ConstantNode('3', config.number), + createConstantNode(3), new OperatorNode('^', 'pow', [ arg1, new OperatorNode('/', 'divide', [ - new ConstantNode('2', config.number), - new ConstantNode('3', config.number) + createConstantNode(2), + createConstantNode(3) ]) ]) ]); @@ -238,10 +241,10 @@ function factory (type, config, load, typed) { case 'sqrt': case 'nthRoot': // d/dx(sqrt(x)) = 1 / (2*sqrt(x)) - if (node.args.length == 1) { + if (node.args.length === 1) { div = true; funcDerivative = new OperatorNode('*', 'multiply', [ - new ConstantNode('2', config.number), + createConstantNode(2), new FunctionNode('sqrt', [arg1]) ]); break; @@ -249,7 +252,7 @@ function factory (type, config, load, typed) { // Rearrange from nthRoot(x, a) -> x^(1/a) arg2 = new OperatorNode('/', 'divide', [ - new ConstantNode('1', config.number), + createConstantNode(1), node.args[1] ]); @@ -258,9 +261,10 @@ function factory (type, config, load, typed) { return _derivative(new OperatorNode('^', 'pow', [arg1, arg2]), constNodes); case 'log10': - arg2 = new ConstantNode('10', config.number); + arg2 = createConstantNode(10); + /* fall through! */ case 'log': - if (!arg2 && node.args.length == 1) { + if (!arg2 && node.args.length === 1) { // d/dx(log(x)) = 1 / x funcDerivative = arg1.clone(); } else if (arg2 || constNodes[node.args[1]] !== undefined) { @@ -297,7 +301,7 @@ function factory (type, config, load, typed) { // d/dx(tan(x)) = sec(x)^2 funcDerivative = new OperatorNode('^', 'pow', [ new FunctionNode('sec', [arg1.clone()]), - new ConstantNode('2', config.number) + createConstantNode(2) ]); break; case 'sec': @@ -320,7 +324,7 @@ function factory (type, config, load, typed) { negative = true; funcDerivative = new OperatorNode('^', 'pow', [ new FunctionNode('csc', [arg1.clone()]), - new ConstantNode('2', config.number) + createConstantNode(2) ]); break; case 'asin': @@ -328,10 +332,10 @@ function factory (type, config, load, typed) { div = true; funcDerivative = new FunctionNode('sqrt', [ new OperatorNode('-', 'subtract', [ - new ConstantNode('1', config.number), + createConstantNode(1), new OperatorNode('^', 'pow', [ arg1.clone(), - new ConstantNode('2', config.number) + createConstantNode(2) ]) ]) ]); @@ -342,10 +346,10 @@ function factory (type, config, load, typed) { negative = true; funcDerivative = new FunctionNode('sqrt', [ new OperatorNode('-', 'subtract', [ - new ConstantNode('1', config.number), + createConstantNode(1), new OperatorNode('^', 'pow', [ arg1.clone(), - new ConstantNode('2', config.number) + createConstantNode(2) ]) ]) ]); @@ -356,9 +360,9 @@ function factory (type, config, load, typed) { funcDerivative = new OperatorNode('+', 'add', [ new OperatorNode('^', 'pow', [ arg1.clone(), - new ConstantNode('2', config.number) + createConstantNode(2) ]), - new ConstantNode('1', config.number) + createConstantNode(1) ]); break; case 'asec': @@ -370,9 +374,9 @@ function factory (type, config, load, typed) { new OperatorNode('-', 'subtract', [ new OperatorNode('^', 'pow', [ arg1.clone(), - new ConstantNode('2', config.number) + createConstantNode(2) ]), - new ConstantNode('1', config.number) + createConstantNode(1) ]) ]) ]); @@ -387,9 +391,9 @@ function factory (type, config, load, typed) { new OperatorNode('-', 'subtract', [ new OperatorNode('^', 'pow', [ arg1.clone(), - new ConstantNode('2', config.number) + createConstantNode(2) ]), - new ConstantNode('1', config.number) + createConstantNode(1) ]) ]) ]); @@ -401,9 +405,9 @@ function factory (type, config, load, typed) { funcDerivative = new OperatorNode('+', 'add', [ new OperatorNode('^', 'pow', [ arg1.clone(), - new ConstantNode('2', config.number) + createConstantNode(2) ]), - new ConstantNode('1', config.number) + createConstantNode(1) ]); break; case 'sinh': @@ -418,7 +422,7 @@ function factory (type, config, load, typed) { // d/dx(tanh(x)) = sech(x)^2 funcDerivative = new OperatorNode('^', 'pow', [ new FunctionNode('sech', [arg1.clone()]), - new ConstantNode('2', config.number) + createConstantNode(2) ]); break; case 'sech': @@ -442,7 +446,7 @@ function factory (type, config, load, typed) { negative = true; funcDerivative = new OperatorNode('^', 'pow', [ new FunctionNode('csch', [arg1.clone()]), - new ConstantNode('2', config.number) + createConstantNode(2) ]); break; case 'asinh': @@ -452,9 +456,9 @@ function factory (type, config, load, typed) { new OperatorNode('+', 'add', [ new OperatorNode('^', 'pow', [ arg1.clone(), - new ConstantNode('2', config.number) + createConstantNode(2) ]), - new ConstantNode('1', config.number) + createConstantNode(1) ]) ]); break; @@ -465,9 +469,9 @@ function factory (type, config, load, typed) { new OperatorNode('-', 'subtract', [ new OperatorNode('^', 'pow', [ arg1.clone(), - new ConstantNode('2', config.number) + createConstantNode(2) ]), - new ConstantNode('1', config.number), + createConstantNode(1) ]) ]); break; @@ -475,10 +479,10 @@ function factory (type, config, load, typed) { // d/dx(atanh(x)) = 1 / (1 - x^2) div = true; funcDerivative = new OperatorNode('-', 'subtract', [ - new ConstantNode('1', config.number), + createConstantNode(1), new OperatorNode('^', 'pow', [ arg1.clone(), - new ConstantNode('2', config.number) + createConstantNode(2) ]) ]); break; @@ -490,10 +494,10 @@ function factory (type, config, load, typed) { arg1.clone(), new FunctionNode('sqrt', [ new OperatorNode('-', 'subtract', [ - new ConstantNode('1', config.number), + createConstantNode(1), new OperatorNode('^', 'pow', [ arg1.clone(), - new ConstantNode('2', config.number) + createConstantNode(2) ]) ]) ]) @@ -509,9 +513,9 @@ function factory (type, config, load, typed) { new OperatorNode('+', 'add', [ new OperatorNode('^', 'pow', [ arg1.clone(), - new ConstantNode('2', config.number) + createConstantNode(2) ]), - new ConstantNode('1', config.number) + createConstantNode(1) ]) ]) ]); @@ -521,10 +525,10 @@ function factory (type, config, load, typed) { div = true; negative = true; funcDerivative = new OperatorNode('-', 'subtract', [ - new ConstantNode('1', config.number), + createConstantNode(1), new OperatorNode('^', 'pow', [ arg1.clone(), - new ConstantNode('2', config.number) + createConstantNode(2) ]) ]); break; @@ -560,7 +564,7 @@ function factory (type, config, load, typed) { 'OperatorNode, Object': function (node, constNodes) { if (constNodes[node] !== undefined) { - return new ConstantNode('0', config.number); + return createConstantNode(0); } var arg1 = node.args[0]; @@ -574,7 +578,7 @@ function factory (type, config, load, typed) { })); case '-': // d/dx(+/-f(x)) = +/-f'(x) - if (node.args.length == 1) { + if (node.args.length === 1) { return new OperatorNode(node.op, node.fn, [_derivative(arg1, constNodes)]); } @@ -623,7 +627,7 @@ function factory (type, config, load, typed) { new OperatorNode('-', 'unaryMinus', [arg1]), new OperatorNode('/', 'divide', [ _derivative(arg2, constNodes), - new OperatorNode('^', 'pow', [arg2.clone(), new ConstantNode('2', config.number)]) + new OperatorNode('^', 'pow', [arg2.clone(), createConstantNode(2)]) ]) ]); } @@ -634,13 +638,13 @@ function factory (type, config, load, typed) { new OperatorNode('*', 'multiply', [_derivative(arg1, constNodes), arg2.clone()]), new OperatorNode('*', 'multiply', [arg1.clone(), _derivative(arg2, constNodes)]) ]), - new OperatorNode('^', 'pow', [arg2.clone(), new ConstantNode('2', config.number)]) + new OperatorNode('^', 'pow', [arg2.clone(), createConstantNode(2)]) ]); case '^': if (constNodes[arg1] !== undefined) { // If is secretly constant; 0^f(x) = 1 (in JS), 1^f(x) = 1 - if (type.isConstantNode(arg1) && (arg1.value === '0' || arg1.value === '1')) { - return new ConstantNode('0', config.number); + if (type.isConstantNode(arg1) && (isZero(arg1.value) || equal(arg1.value, 1))) { + return createConstantNode(0); } // d/dx(c^f(x)) = c^f(x)*ln(c)*f'(x) @@ -655,14 +659,12 @@ function factory (type, config, load, typed) { if (constNodes[arg2] !== undefined) { if (type.isConstantNode(arg2)) { - var expValue = arg2.value; - // If is secretly constant; f(x)^0 = 1 -> d/dx(1) = 0 - if (expValue === '0') { - return new ConstantNode('0', config.number); + if (isZero(arg2.value)) { + return createConstantNode(0); } // Ignore exponent; f(x)^1 = f(x) - if (expValue === '1') { + if (equal(arg2.value, 1)) { return _derivative(arg1, constNodes); } } @@ -672,7 +674,7 @@ function factory (type, config, load, typed) { arg1.clone(), new OperatorNode('-', 'subtract', [ arg2, - new ConstantNode('1', config.number) + createConstantNode(1) ]) ]); @@ -681,7 +683,7 @@ function factory (type, config, load, typed) { new OperatorNode('*', 'multiply', [ _derivative(arg1, constNodes), powMinusOne - ]), + ]) ]); } @@ -714,7 +716,7 @@ function factory (type, config, load, typed) { */ function funcArgsCheck(node) { //TODO add min, max etc - if ((node.name == 'log' || node.name == 'nthRoot') && node.args.length == 2) { + if ((node.name === 'log' || node.name === 'nthRoot') && node.args.length === 2) { return; } @@ -723,13 +725,23 @@ function factory (type, config, load, typed) { // Change all args to constants to avoid unidentified // symbol error when compiling function for (var i = 0; i < node.args.length; ++i) { - node.args[i] = new ConstantNode(0); + node.args[i] = createConstantNode(0); } node.compile().eval(); throw new Error('Expected TypeError, but none found'); } + /** + * Helper function to create a constant node with a specific type + * (number, BigNumber, Fraction) + * @param {number} value + * @param {string} [valueType] + * @return {ConstantNode} + */ + function createConstantNode(value, valueType) { + return new ConstantNode(createNumericValue(value, valueType || config.number)); + } return derivative; } diff --git a/lib/function/algebra/simplify.js b/lib/function/algebra/simplify.js index 9ad5df329..ae4f3a343 100644 --- a/lib/function/algebra/simplify.js +++ b/lib/function/algebra/simplify.js @@ -3,6 +3,7 @@ function factory (type, config, load, typed, math) { var parse = load(require('../../expression/parse')); + var equal = load(require('../relational/equal')); var ConstantNode = load(require('../../expression/node/ConstantNode')); var FunctionNode = load(require('../../expression/node/FunctionNode')); var OperatorNode = load(require('../../expression/node/OperatorNode')); @@ -599,7 +600,7 @@ function factory (type, config, load, typed, math) { } else if (rule instanceof ConstantNode) { // Literal constant must match exactly - if(rule.value !== node.value) { + if(!equal(rule.value, node.value)) { return []; } } @@ -624,7 +625,7 @@ function factory (type, config, load, typed, math) { */ function _exactMatch(p, q) { if(p instanceof ConstantNode && q instanceof ConstantNode) { - if(p.value !== q.value) { + if(!equal(p.value, q.value)) { return false; } } diff --git a/lib/function/algebra/simplify/simplifyConstant.js b/lib/function/algebra/simplify/simplifyConstant.js index 8b22ab15e..8950e26bf 100644 --- a/lib/function/algebra/simplify/simplifyConstant.js +++ b/lib/function/algebra/simplify/simplifyConstant.js @@ -4,6 +4,7 @@ var digits = require('./../../../utils/number').digits; // TODO this could be improved by simplifying seperated constants under associative and commutative operators function factory(type, config, load, typed, math) { var util = load(require('./util')); + var isNumeric = load(require('../../utils/isNumeric')); var isCommutative = util.isCommutative; var isAssociative = util.isAssociative; var allChildren = util.allChildren; @@ -151,7 +152,7 @@ function factory(type, config, load, typed, math) { case 'SymbolNode': return node; case 'ConstantNode': - if (node.valueType === 'number') { + if (typeof node.value === 'number') { return _toNumber(node.value); } return node; diff --git a/lib/function/algebra/simplify/simplifyCore.js b/lib/function/algebra/simplify/simplifyCore.js index f5e1cd5e7..a1ca57424 100644 --- a/lib/function/algebra/simplify/simplifyCore.js +++ b/lib/function/algebra/simplify/simplifyCore.js @@ -1,6 +1,15 @@ 'use strict'; function factory(type, config, load, typed, math) { + var equal = load(require('../../relational/equal')); + var isZero = load(require('../../utils/isZero')); + var isNumeric = load(require('../../utils/isNumeric')); + var add = load(require('../../arithmetic/add')); + var subtract = load(require('../../arithmetic/subtract')); + var multiply = load(require('../../arithmetic/multiply')); + var divide = load(require('../../arithmetic/divide')); + var pow = load(require('../../arithmetic/pow')); + var ConstantNode = math.expression.node.ConstantNode; var OperatorNode = math.expression.node.OperatorNode; var FunctionNode = math.expression.node.FunctionNode; @@ -41,13 +50,13 @@ function factory(type, config, load, typed, math) { return node.args[0]; } if (type.isConstantNode(a0)) { - if (a0.value === "0") { + if (isZero(a0.value)) { return a1; - } else if (type.isConstantNode(a1) && a0.value && a0.value.length < 5 && a1.value && a1.value.length < 5) { - return new ConstantNode(Number(a0.value) + Number(a1.value)); + } else if (type.isConstantNode(a1)) { + return new ConstantNode(add(a0.value, a1.value)); } } - if (type.isConstantNode(a1) && a1.value === "0") { + if (type.isConstantNode(a1) && isZero(a1.value)) { return a0; } if (node.args.length === 2 && type.isOperatorNode(a1) && a1.op === '-' && a1.fn === 'unaryMinus') { @@ -56,14 +65,14 @@ function factory(type, config, load, typed, math) { return new OperatorNode(node.op, node.fn, a1 ? [a0,a1] : [a0]); } else if (node.op === "-") { if (type.isConstantNode(a0) && a1) { - if (type.isConstantNode(a1) && a0.value && a0.value.length < 5 && a1.value && a1.value.length < 5) { - return new ConstantNode(Number(a0.value) - Number(a1.value)); - } else if (a0.value === "0") { + if (type.isConstantNode(a1)) { + return new ConstantNode(subtract(a0.value, a1.value)); + } else if (isZero(a0.value)) { return new OperatorNode("-", "unaryMinus", [a1]); } } if (node.fn === "subtract" && node.args.length === 2) { - if (type.isConstantNode(a1) && a1.value === "0") { + if (type.isConstantNode(a1) && isZero(a1.value)) { return a0; } if (type.isOperatorNode(a1) && a1.fn === "unaryMinus") { @@ -83,23 +92,23 @@ function factory(type, config, load, typed, math) { throw new Error('never happens'); } else if (node.op === "*") { if (type.isConstantNode(a0)) { - if (a0.value === "0") { + if (isZero(a0.value)) { return node0; - } else if (a0.value === "1") { + } else if (equal(a0.value, 1)) { return a1; - } else if (type.isConstantNode(a1) && a0.value && a0.value.length < 5 && a1.value && a1.value.length < 5) { - return new ConstantNode(Number(a0.value) * Number(a1.value)); + } else if (type.isConstantNode(a1)) { + return new ConstantNode(multiply(a0.value, a1.value)); } } if (type.isConstantNode(a1)) { - if (a1.value === "0") { + if (isZero(a1.value)) { return node0; - } else if (a1.value === "1") { + } else if (equal(a1.value, 1)) { return a0; } else if (type.isOperatorNode(a0) && a0.op === node.op) { var a00 = a0.args[0]; - if (type.isConstantNode(a00) && a1.value && a1.value.length < 5 && a00.value && a00.value.length < 5) { - var a00_a1 = new ConstantNode(Number(a0.args[0].value) * Number(a1.value)); + if (type.isConstantNode(a00)) { + var a00_a1 = new ConstantNode(multiply(a00.value, a1.value)); return new OperatorNode(node.op, node.fn, [a00_a1, a0.args[1]]); // constants on left } } @@ -108,32 +117,30 @@ function factory(type, config, load, typed, math) { return new OperatorNode(node.op, node.fn, [a0, a1]); } else if (node.op === "/") { if (type.isConstantNode(a0)) { - if (a0.value === "0") { + if (isZero(a0.value)) { return node0; - } else if (type.isConstantNode(a1) && a0.value && a0.value.length < 5 && (a1.value === "1" || a1.value==="2" || a1.value==="4")) { - return new ConstantNode(Number(a0.value) / Number(a1.value)); + } else if (type.isConstantNode(a1) && + (equal(a1.value, 1) || equal(a1.value, 2) || equal(a1.value, 4))) { + return new ConstantNode(divide(a0.value, a1.value)); } } return new OperatorNode(node.op, node.fn, [a0, a1]); } else if (node.op === "^") { if (type.isConstantNode(a1)) { - if (a1.value === "0") { + if (isZero(a1.value)) { return node1; - } else if (a1.value === "1") { + } else if (equal(a1.value, 1)) { return a0; } else { - if (type.isConstantNode(a0) && - a0.value && a0.value.length < 5 && - a1.value && a1.value.length < 2) { + if (type.isConstantNode(a0)) { // fold constant - return new ConstantNode( - math.pow(Number(a0.value), Number(a1.value))); + return new ConstantNode(pow(a0.value, a1.value)); } else if (type.isOperatorNode(a0) && a0.op === "^") { var a01 = a0.args[1]; if (type.isConstantNode(a01)) { return new OperatorNode(node.op, node.fn, [ a0.args[0], - new ConstantNode(a01.value * a1.value) + new ConstantNode(multiply(a01.value, a1.value)) ]); } } diff --git a/lib/function/algebra/utils/createNumericValue.js b/lib/function/algebra/utils/createNumericValue.js new file mode 100644 index 000000000..d855f872a --- /dev/null +++ b/lib/function/algebra/utils/createNumericValue.js @@ -0,0 +1,31 @@ +'use strict'; + +function factory(type, config, load, typed) { + + /** + * Create a numeric value with a specific type: number, BigNumber, or Fraction + * + * @param {string | number} value + * @param {'number' | 'BigNumber' | 'Fraction'} + * @return {number | BigNumber | Fraction} Returns an instance of the + * requested type + */ + return function createNumericValue (value, valueType) { + if (valueType === 'BigNumber') { + return new type.BigNumber(value); + } + + if (valueType === 'Fraction') { + return new type.Fraction(value); + } + + if (typeof value === 'number') { + return value; + } + else { + return parseFloat(value); + } + } +} + +exports.factory = factory; diff --git a/test/expression/node/AccessorNode.test.js b/test/expression/node/AccessorNode.test.js index 156913cf9..3778c4544 100644 --- a/test/expression/node/AccessorNode.test.js +++ b/test/expression/node/AccessorNode.test.js @@ -411,7 +411,7 @@ describe('AccessorNode', function() { return string; } else if (node.type === 'ConstantNode') { - return 'const(' + node.value + ', ' + node.valueType + ')' + return 'const(' + node.value + ', ' + math.typeof(node.value) + ')' } }; @@ -450,7 +450,7 @@ describe('AccessorNode', function() { return latex; } else if (node.type === 'ConstantNode') { - return 'const\\left(' + node.value + ', ' + node.valueType + '\\right)' + return 'const\\left(' + node.value + ', ' + math.typeof(node.value) + '\\right)' } }; diff --git a/test/expression/node/ArrayNode.test.js b/test/expression/node/ArrayNode.test.js index 5702ccc2d..7f9c31b29 100644 --- a/test/expression/node/ArrayNode.test.js +++ b/test/expression/node/ArrayNode.test.js @@ -266,7 +266,7 @@ describe('ArrayNode', function() { return string; } else if (node.type === 'ConstantNode') { - return 'const(' + node.value + ', ' + node.valueType + ')' + return 'const(' + node.value + ', ' + math.typeof(node.value) + ')' } }; @@ -303,7 +303,7 @@ describe('ArrayNode', function() { return latex; } else if (node.type === 'ConstantNode') { - return 'const\\left(' + node.value + ', ' + node.valueType + '\\right)' + return 'const\\left(' + node.value + ', ' + math.typeof(node.value) + '\\right)' } }; diff --git a/test/expression/node/AssignmentNode.test.js b/test/expression/node/AssignmentNode.test.js index 74637e660..5bab826ae 100644 --- a/test/expression/node/AssignmentNode.test.js +++ b/test/expression/node/AssignmentNode.test.js @@ -153,7 +153,7 @@ describe('AssignmentNode', function() { new bigmath.expression.node.ConstantNode(2), new bigmath.expression.node.ConstantNode(1) ]); - var value = new bigmath.expression.node.ConstantNode(5); + var value = new bigmath.expression.node.ConstantNode(bigmath.bignumber(5)); var n = new bigmath.expression.node.AssignmentNode(object, index, value); var expr = n.compile(); @@ -190,8 +190,8 @@ describe('AssignmentNode', function() { assert.deepEqual(n.filter(function (node) {return node.isAssignmentNode}), [n]); assert.deepEqual(n.filter(function (node) {return node.isSymbolNode}), [a]); assert.deepEqual(n.filter(function (node) {return node.isConstantNode}), [b, c, v]); - assert.deepEqual(n.filter(function (node) {return node.value === '1'}), [c]); - assert.deepEqual(n.filter(function (node) {return node.value === '2'}), [b, v]); + assert.deepEqual(n.filter(function (node) {return node.value === 1}), [c]); + assert.deepEqual(n.filter(function (node) {return node.value === 2}), [b, v]); assert.deepEqual(n.filter(function (node) {return node.name === 'q'}), []); }); @@ -203,7 +203,7 @@ describe('AssignmentNode', function() { assert.deepEqual(n.filter(function (node) {return node.isAssignmentNode}), [n]); assert.deepEqual(n.filter(function (node) {return node.isSymbolNode}), [a]); assert.deepEqual(n.filter(function (node) {return node.isConstantNode}), [v]); - assert.deepEqual(n.filter(function (node) {return node.value === '2'}), [v]); + assert.deepEqual(n.filter(function (node) {return node.value === 2}), [v]); assert.deepEqual(n.filter(function (node) {return node.name === 'q'}), []); }); @@ -498,7 +498,7 @@ describe('AssignmentNode', function() { ' equals ' + node.value.toString(options); } else if (node.type === 'ConstantNode') { - return 'const(' + node.value + ', ' + node.valueType + ')' + return 'const(' + node.value + ', ' + math.typeof(node.value) + ')' } }; @@ -533,7 +533,7 @@ describe('AssignmentNode', function() { '\\mbox{equals}' + node.value.toTex(options); } else if (node.type === 'ConstantNode') { - return 'const\\left(' + node.value + ', ' + node.valueType + '\\right)' + return 'const\\left(' + node.value + ', ' + math.typeof(node.value) + '\\right)' } }; diff --git a/test/expression/node/BlockNode.test.js b/test/expression/node/BlockNode.test.js index 21ec0c773..2797ef196 100644 --- a/test/expression/node/BlockNode.test.js +++ b/test/expression/node/BlockNode.test.js @@ -281,7 +281,7 @@ describe('BlockNode', function() { return string; } else if (node.type === 'ConstantNode') { - return 'const(' + node.value + ', ' + node.valueType + ')' + return 'const(' + node.value + ', ' + math.typeof(node.value) + ')' } }; @@ -315,7 +315,7 @@ describe('BlockNode', function() { return latex; } else if (node.type === 'ConstantNode') { - return 'const\\left(' + node.value + ', ' + node.valueType + '\\right)' + return 'const\\left(' + node.value + ', ' + math.typeof(node.value) + '\\right)' } }; diff --git a/test/expression/node/ConditionalNode.test.js b/test/expression/node/ConditionalNode.test.js index 63468bb13..9a47dac81 100644 --- a/test/expression/node/ConditionalNode.test.js +++ b/test/expression/node/ConditionalNode.test.js @@ -288,7 +288,7 @@ describe('ConditionalNode', function() { + ' else ' + node.falseExpr.toString(options); } else if (node.type === 'ConstantNode') { - return 'const(' + node.value + ', ' + node.valueType + ')' + return 'const(' + node.value + ', ' + math.typeof(node.value) + ')' } }; @@ -317,7 +317,7 @@ describe('ConditionalNode', function() { + ' else ' + node.falseExpr.toTex(options); } else if (node.type === 'ConstantNode') { - return 'const\\left(' + node.value + ', ' + node.valueType + '\\right)' + return 'const\\left(' + node.value + ', ' + math.typeof(node.value) + '\\right)' } }; diff --git a/test/expression/node/ConstantNode.test.js b/test/expression/node/ConstantNode.test.js index 4bdaf21d0..8bca81aed 100644 --- a/test/expression/node/ConstantNode.test.js +++ b/test/expression/node/ConstantNode.test.js @@ -9,24 +9,18 @@ var SymbolNode = math.expression.node.SymbolNode; describe('ConstantNode', function() { - it ('should create a ConstantNode with value type', function () { - var a = new ConstantNode('3', 'number'); - assert(a instanceof Node); - assert.equal(a.type, 'ConstantNode'); - }); - - it ('should create a ConstantNode without value type', function () { + it ('should create a ConstantNode', function () { var a = new ConstantNode(3); assert(a instanceof Node); assert.equal(a.type, 'ConstantNode'); // TODO: extensively test each of the supported types - assert.deepEqual(new ConstantNode(3), new ConstantNode('3', 'number')); - assert.deepEqual(new ConstantNode('hello'), new ConstantNode('hello', 'string')); - assert.deepEqual(new ConstantNode(true), new ConstantNode('true', 'boolean')); - assert.deepEqual(new ConstantNode(false), new ConstantNode('false', 'boolean')); - assert.deepEqual(new ConstantNode(null), new ConstantNode('null', 'null')); - assert.deepEqual(new ConstantNode(undefined), new ConstantNode('undefined', 'undefined')); + assert.strictEqual(new ConstantNode(3).value, 3); + assert.strictEqual(new ConstantNode('hello').value, 'hello'); + assert.strictEqual(new ConstantNode(true).value, true); + assert.strictEqual(new ConstantNode(false).value, false); + assert.strictEqual(new ConstantNode(null).value, null); + assert.strictEqual(new ConstantNode(undefined).value, undefined); }); it ('should have isConstantNode', function () { @@ -35,68 +29,50 @@ describe('ConstantNode', function() { }); it ('should throw an error when calling without new operator', function () { - assert.throws(function () {ConstantNode('3', 'number')}, SyntaxError); - }); - - it ('should throw an error in case of wrong construction arguments', function () { - assert.throws(function () {new ConstantNode(3, 'number');}, TypeError); - assert.throws(function () {new ConstantNode(new Date());}, TypeError); - assert.throws(function () {new ConstantNode('3', Number);}, TypeError); - }); - - it ('should throw an error in case of unknown type of constant', function () { - assert.throws(function () {new ConstantNode('3', 'bla').compile();}, TypeError); + assert.throws(function () {ConstantNode(3)}, SyntaxError); }); it ('should compile a ConstantNode', function () { - var expr = new ConstantNode('2.3', 'number').compile(); + var expr = new ConstantNode(2.3).compile(); assert.strictEqual(expr.eval(), 2.3); - expr = new ConstantNode('002.3', 'number').compile(); + expr = new ConstantNode(2.3).compile(); assert.strictEqual(expr.eval(), 2.3); - expr = new ConstantNode('hello', 'string').compile(); + expr = new ConstantNode('hello').compile(); assert.strictEqual(expr.eval(), 'hello'); - expr = new ConstantNode('true', 'boolean').compile(); + expr = new ConstantNode(true).compile(); assert.strictEqual(expr.eval(), true); - expr = new ConstantNode('undefined', 'undefined').compile(); + expr = new ConstantNode(undefined).compile(); assert.strictEqual(expr.eval(), undefined); - expr = new ConstantNode('null', 'null').compile(); + expr = new ConstantNode(null).compile(); assert.strictEqual(expr.eval(), null); }); it ('should compile a ConstantNode with bigmath', function () { - var expr = new bigmath.expression.node.ConstantNode('2.3', 'number').compile(); + var constantNode = bigmath.parse('2.3'); + assert.ok(constantNode.isConstantNode); + var expr = constantNode.compile(); assert.deepEqual(expr.eval(), new bigmath.type.BigNumber(2.3)); }); it ('should find a ConstantNode', function () { - var a = new ConstantNode('2', 'number'); + var a = new ConstantNode(2); assert.deepEqual(a.filter(function (node) {return node instanceof ConstantNode}), [a]); assert.deepEqual(a.filter(function (node) {return node instanceof SymbolNode}), []); }); - it ('should throw an error when compiling an invalid value', function () { - var clone = math.create(); - clone.config({number: 'number'}); - assert.throws(function () { new ConstantNode('console.log("foo")', 'number').compile() }, /Invalid numeric value/) - clone.config({number: 'BigNumber'}); - assert.throws(function () { new ConstantNode('console.log("foo")', 'number').compile() }, /Invalid numeric value/) - clone.config({number: 'Fraction'}); - assert.throws(function () { new ConstantNode('console.log("foo")', 'number').compile() }, /Invalid numeric value/) - }); - it ('should leave quotes in strings as is (no escaping)', function () { - assert.strictEqual( new ConstantNode('"+foo+"', 'string').compile().eval(), '"+foo+"') - assert.strictEqual( new ConstantNode('\\"escaped\\"', 'string').compile().eval(), '\\"escaped\\"') + assert.strictEqual( new ConstantNode('"+foo+"').compile().eval(), '"+foo+"') + assert.strictEqual( new ConstantNode('\\"escaped\\"').compile().eval(), '\\"escaped\\"') }); it ('should find a ConstantNode', function () { - var a = new ConstantNode('2', 'number'); + var a = new ConstantNode(2); assert.deepEqual(a.filter(function (node) {return node instanceof ConstantNode}), [a]); assert.deepEqual(a.filter(function (node) {return node instanceof SymbolNode}), []); }); @@ -152,61 +128,60 @@ describe('ConstantNode', function() { assert.strictEqual(a.equals(undefined), false); assert.strictEqual(a.equals(new ConstantNode(2)), true); assert.strictEqual(a.equals(new ConstantNode(3)), false); - assert.strictEqual(a.equals(new ConstantNode('2', 'number')), true); - assert.strictEqual(a.equals(new ConstantNode('2', 'string')), false); + assert.strictEqual(a.equals(new ConstantNode('2')), false); assert.strictEqual(a.equals(new SymbolNode('2')), false); - assert.strictEqual(a.equals({value:2, valueType: 'number'}), false); + assert.strictEqual(a.equals({value:2}), false); }); it ('should stringify a ConstantNode', function () { - assert.equal(new ConstantNode('3', 'number').toString(), '3'); - assert.deepEqual(new ConstantNode('3', 'number').toString(), '3'); - assert.equal(new ConstantNode('hi', 'string').toString(), '"hi"'); - assert.equal(new ConstantNode('true', 'boolean').toString(), 'true'); - assert.equal(new ConstantNode('false', 'boolean').toString(), 'false'); - assert.equal(new ConstantNode('undefined', 'undefined').toString(), 'undefined'); - assert.equal(new ConstantNode('null', 'null').toString(), 'null'); + assert.equal(new ConstantNode(3).toString(), '3'); + assert.deepEqual(new ConstantNode(3).toString(), '3'); + assert.equal(new ConstantNode('hi').toString(), '"hi"'); + assert.equal(new ConstantNode(true).toString(), 'true'); + assert.equal(new ConstantNode(false).toString(), 'false'); + assert.equal(new ConstantNode(undefined).toString(), 'undefined'); + assert.equal(new ConstantNode(null).toString(), 'null'); }); it ('should stringify a ConstantNode with custom toString', function () { //Also checks if the custom functions get passed on to the children var customFunction = function (node, options) { if (node.type === 'ConstantNode') { - return 'const(' + node.value + ', ' + node.valueType + ')' + return 'const(' + node.value + ')' } }; var n = new ConstantNode(1); - assert.equal(n.toString({handler: customFunction}), 'const(1, number)'); + assert.equal(n.toString({handler: customFunction}), 'const(1)'); }); it ('should LaTeX a ConstantNode', function () { - assert.equal(new ConstantNode('3', 'number').toTex(), '3'); - assert.deepEqual(new ConstantNode('3', 'number').toTex(), '3'); - assert.equal(new ConstantNode('hi', 'string').toTex(), '\\mathtt{"hi"}'); - assert.equal(new ConstantNode('true', 'boolean').toTex(), 'true'); - assert.equal(new ConstantNode('false', 'boolean').toTex(), 'false'); - assert.equal(new ConstantNode('undefined', 'undefined').toTex(), 'undefined'); - assert.equal(new ConstantNode('null', 'null').toTex(), 'null'); + assert.equal(new ConstantNode(3).toTex(), '3'); + assert.deepEqual(new ConstantNode(3).toTex(), '3'); + assert.equal(new ConstantNode('hi').toTex(), '\\mathtt{"hi"}'); + assert.equal(new ConstantNode(true).toTex(), 'true'); + assert.equal(new ConstantNode(false).toTex(), 'false'); + assert.equal(new ConstantNode(undefined).toTex(), 'undefined'); + assert.equal(new ConstantNode(null).toTex(), 'null'); }); it ('should LaTeX a ConstantNode in exponential notation', function () { - var n = new ConstantNode('1e10', 'number'); - assert.equal(n.toTex(), '1\\cdot10^{10}'); + var n = new ConstantNode(1e10); + assert.equal(n.toTex(), '1\\cdot10^{+10}'); }); it ('should LaTeX a ConstantNode with custom toTex', function () { //Also checks if the custom functions get passed on to the children var customFunction = function (node, options) { if (node.type === 'ConstantNode') { - return 'const\\left(' + node.value + ', ' + node.valueType + '\\right)' + return 'const\\left(' + node.value + '\\right)' } }; var n = new ConstantNode(1); - assert.equal(n.toTex({handler: customFunction}), 'const\\left(1, number\\right)'); + assert.equal(n.toTex({handler: customFunction}), 'const\\left(1\\right)'); }); }); diff --git a/test/expression/node/FunctionAssignmentNode.test.js b/test/expression/node/FunctionAssignmentNode.test.js index 28e391fb5..29c1f7fe4 100644 --- a/test/expression/node/FunctionAssignmentNode.test.js +++ b/test/expression/node/FunctionAssignmentNode.test.js @@ -381,7 +381,7 @@ describe('FunctionAssignmentNode', function() { return string; } else if (node.type === 'ConstantNode') { - return 'const(' + node.value + ', ' + node.valueType + ')' + return 'const(' + node.value + ', ' + math.typeof(node.value) + ')' } }; @@ -424,7 +424,7 @@ describe('FunctionAssignmentNode', function() { return latex; } else if (node.type === 'ConstantNode') { - return 'const\\left(' + node.value + ', ' + node.valueType + '\\right)' + return 'const\\left(' + node.value + ', ' + math.typeof(node.value) + '\\right)' } }; diff --git a/test/expression/node/FunctionNode.test.js b/test/expression/node/FunctionNode.test.js index 0339a2447..643e2d897 100644 --- a/test/expression/node/FunctionNode.test.js +++ b/test/expression/node/FunctionNode.test.js @@ -410,7 +410,7 @@ describe('FunctionNode', function() { return string; } else if (node.type === 'ConstantNode') { - return 'const(' + node.value + ', ' + node.valueType + ')' + return 'const(' + node.value + ', ' + math.typeof(node.value) + ')' } }; @@ -478,7 +478,7 @@ describe('FunctionNode', function() { return latex; } else if (node.type === 'ConstantNode') { - return 'const\\left(' + node.value + ', ' + node.valueType + '\\right)' + return 'const\\left(' + node.value + ', ' + math.typeof(node.value) + '\\right)' } }; diff --git a/test/expression/node/IndexNode.test.js b/test/expression/node/IndexNode.test.js index 4f8a9020c..f369b82d4 100644 --- a/test/expression/node/IndexNode.test.js +++ b/test/expression/node/IndexNode.test.js @@ -85,7 +85,7 @@ describe('IndexNode', function() { paths.push(path); assert.strictEqual(parent, n); - return node.isConstantNode && node.value === '1' ? e : node; + return node.isConstantNode && node.value === 1 ? e : node; }); assert.equal(nodes.length, 2); @@ -115,7 +115,7 @@ describe('IndexNode', function() { var e = new SymbolNode('c'); var f = n.transform(function (node) { - return node.isConstantNode && node.value === '1' ? e : node; + return node.isConstantNode && node.value === 1 ? e : node; }); assert.notStrictEqual(f, n); @@ -197,7 +197,7 @@ describe('IndexNode', function() { }).join(', '); } else if (node.type === 'ConstantNode') { - return 'const(' + node.value + ', ' + node.valueType + ')' + return 'const(' + node.value + ', ' + math.typeof(node.value) + ')' } }; @@ -240,7 +240,7 @@ describe('IndexNode', function() { return latex; } else if (node.type === 'ConstantNode') { - return 'const\\left(' + node.value + ', ' + node.valueType + '\\right)' + return 'const\\left(' + node.value + ', ' + math.typeof(node.value) + '\\right)' } }; diff --git a/test/expression/node/ObjectNode.test.js b/test/expression/node/ObjectNode.test.js index 628f525a7..544255385 100644 --- a/test/expression/node/ObjectNode.test.js +++ b/test/expression/node/ObjectNode.test.js @@ -247,7 +247,7 @@ describe('ObjectNode', function() { it ('should stringify an ObjectNode with custom toString', function () { var customFunction = function (node, options) { if (node.type === 'ConstantNode') { - return 'const(' + node.value + ', ' + node.valueType + ')' + return 'const(' + node.value + ', ' + math.typeof(node.value) + ')' } }; @@ -271,7 +271,7 @@ describe('ObjectNode', function() { it ('should LaTeX an ObjectNode with custom toTex', function () { var customFunction = function (node, options) { if (node.type === 'ConstantNode') { - return 'const\\left(' + node.value + ', ' + node.valueType + '\\right)' + return 'const\\left(' + node.value + ', ' + math.typeof(node.value) + '\\right)' } }; diff --git a/test/expression/node/OperatorNode.test.js b/test/expression/node/OperatorNode.test.js index 91a4678c4..46c567f5f 100644 --- a/test/expression/node/OperatorNode.test.js +++ b/test/expression/node/OperatorNode.test.js @@ -359,7 +359,7 @@ describe('OperatorNode', function() { + ', ' + node.args[1].toString(options) + ')'; } else if (node.type === 'ConstantNode') { - return 'const(' + node.value + ', ' + node.valueType + ')' + return 'const(' + node.value + ', ' + math.typeof(node.value) + ')' } }; @@ -382,7 +382,7 @@ describe('OperatorNode', function() { node.args[1].toString(options); } else if (node.type === 'ConstantNode') { - return 'const(' + node.value + ', ' + node.valueType + ')' + return 'const(' + node.value + ', ' + math.typeof(node.value) + ')' } }; @@ -583,7 +583,7 @@ describe('OperatorNode', function() { + ', ' + node.args[1].toTex(options) + ')'; } else if (node.type === 'ConstantNode') { - return 'const\\left(' + node.value + ', ' + node.valueType + '\\right)' + return 'const\\left(' + node.value + ', ' + math.typeof(node.value) + '\\right)' } }; @@ -606,7 +606,7 @@ describe('OperatorNode', function() { node.args[1].toTex(options); } else if (node.type === 'ConstantNode') { - return 'const\\left(' + node.value + ', ' + node.valueType + '\\right)' + return 'const\\left(' + node.value + ', ' + math.typeof(node.value) + '\\right)' } }; diff --git a/test/expression/node/RangeNode.test.js b/test/expression/node/RangeNode.test.js index 4d1d17108..642e57b6c 100644 --- a/test/expression/node/RangeNode.test.js +++ b/test/expression/node/RangeNode.test.js @@ -301,7 +301,7 @@ describe('RangeNode', function() { + ' with steps of ' + node.step.toString(options); } else if (node.type === 'ConstantNode') { - return 'const(' + node.value + ', ' + node.valueType + ')' + return 'const(' + node.value + ', ' + math.typeof(node.value) + ')' } }; @@ -345,7 +345,7 @@ describe('RangeNode', function() { + ' with steps of ' + node.step.toTex(options); } else if (node.type === 'ConstantNode') { - return 'const\\left(' + node.value + ', ' + node.valueType + '\\right)' + return 'const\\left(' + node.value + ', ' + math.typeof(node.value) + '\\right)' } }; diff --git a/test/expression/security.test.js b/test/expression/security.test.js index cf74a51eb..2b0dd958f 100644 --- a/test/expression/security.test.js +++ b/test/expression/security.test.js @@ -192,7 +192,7 @@ describe('security', function () { it ('should not allow calling eval via clone', function () { assert.throws(function () { - math.eval('expression.node.ConstantNode.prototype.clone.call({"value":"eval", "valueType":"null"}).eval()("console.log(\'hacked...\')")') + math.eval('expression.node.ConstantNode.prototype.clone.call({"value":"eval"}).eval()("console.log(\'hacked...\')")') }, /Error: Undefined symbol expression/); }) From 3f3d50f7a0202875870f586a09d51eab72271b85 Mon Sep 17 00:00:00 2001 From: jos Date: Tue, 23 Jan 2018 13:55:57 +0100 Subject: [PATCH 2/6] Fixed #833: the constants `true`, `false`, `null`, `undefined`, `NaN`, `Infinity`, and `uninitialized` are now parsed as ConstantNodes instead of SymbolNodes in the expression parser --- HISTORY.md | 3 ++ lib/expression/node/ConstantNode.js | 1 + lib/expression/parse.js | 52 ++++++++++--------- lib/function/algebra/derivative.js | 4 +- .../algebra/utils/createNumericValue.js | 31 ----------- lib/type/numeric.js | 50 ++++++++++++++++++ test/expression/parse.test.js | 30 +++++++++-- test/function/algebra/simplify.test.js | 5 +- 8 files changed, 111 insertions(+), 65 deletions(-) delete mode 100644 lib/function/algebra/utils/createNumericValue.js create mode 100644 lib/type/numeric.js diff --git a/HISTORY.md b/HISTORY.md index c0039fe80..9eeaf7681 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -16,6 +16,9 @@ Breaking changes: `ConstantNode(valueStr, valueType`) is now `ConstantNode(value)` Stringification uses `math.format`, which may result in differently formatted numeric output. +- The constants `true`, `false`, `null`, `undefined`, `NaN`, `Infinity`, + and `uninitialized` are now parsed as ConstantNodes instead of + SymbolNodes in the expression parser. See #833. ## 2018-01-17, version 3.20.1 diff --git a/lib/expression/node/ConstantNode.js b/lib/expression/node/ConstantNode.js index dbcaefb4e..db0f5ebf0 100644 --- a/lib/expression/node/ConstantNode.js +++ b/lib/expression/node/ConstantNode.js @@ -24,6 +24,7 @@ function factory (type, config, load, typed) { } if (arguments.length === 2) { + // TODO: remove deprecation error some day (created 2018-01-23) throw new SyntaxError('new ConstantNode(valueStr, valueType) is not supported anymore since math v4.0.0. Use new ConstantNode(value) instead, where value is a non-stringified value.'); } diff --git a/lib/expression/parse.js b/lib/expression/parse.js index fe96a08f2..3e777c21a 100644 --- a/lib/expression/parse.js +++ b/lib/expression/parse.js @@ -4,6 +4,9 @@ var ArgumentsError = require('../error/ArgumentsError'); var deepMap = require('../utils/collection/deepMap'); function factory (type, config, load, typed) { + var numeric = load(require('../type/numeric')); + var uninitialized = require('../utils/array').UNINITIALIZED + var AccessorNode = load(require('./node/AccessorNode')); var ArrayNode = load(require('./node/ArrayNode')); var AssignmentNode = load(require('./node/AssignmentNode')); @@ -142,8 +145,20 @@ function factory (type, config, load, typed) { 'not': true }; + var CONSTANTS = { + 'true': true, + 'false': false, + 'null': null, + 'undefined': undefined, + 'uninitialized': uninitialized + } + + var NUMERIC_CONSTANTS = [ + 'NaN', + 'Infinity' + ] + var extra_nodes = {}; // current extra nodes - var number_config = 'number' // 'number', 'Fraction', or 'BigNumber' var expression = ''; // current expression var comment = ''; // last parsed comment var index = 0; // current index in expr @@ -1136,8 +1151,17 @@ function factory (type, config, load, typed) { getToken(); + if (CONSTANTS.hasOwnProperty(name)) { // true, false, null, ... + node = new ConstantNode(CONSTANTS[name]); + } + else if (NUMERIC_CONSTANTS.indexOf(name) !== -1) { // NaN, Infinity + node = new ConstantNode(numeric(name)); + } + else { + node = new SymbolNode(name); + } + // parse function parameters and matrix index - node = new SymbolNode(name); node = parseAccessors(node); return node; } @@ -1446,29 +1470,7 @@ function factory (type, config, load, typed) { numberStr = token; getToken(); - if (config.number === 'BigNumber') { - return new ConstantNode(new type.BigNumber(numberStr)); - } - else if (config.number === 'Fraction') { - return new ConstantNode(new type.Fraction(numberStr)); - } - else { - // The following regexp is relatively permissive - if (!/^[\-+]?((\d+\.?\d*)|(\d*\.?\d+))([eE][+\-]?\d+)?$/.test(numberStr)) { - throw new Error('Invalid numeric value "' + numberStr + '"'); - } - - // remove leading zeros like '003.2' which are not allowed by JavaScript - var number = parseFloat(numberStr.replace(/^(0*)[0-9]/, function (match, zeros) { - return match.substring(zeros.length); - })); - - if (isNaN(number)) { - throw new Error('Invalid numeric value "' + numberStr + '"'); - } - - return new ConstantNode(number); - } + return new ConstantNode(numeric(numberStr, config.number)); } return parseParentheses(); diff --git a/lib/function/algebra/derivative.js b/lib/function/algebra/derivative.js index 26ca73354..bcb7c2476 100644 --- a/lib/function/algebra/derivative.js +++ b/lib/function/algebra/derivative.js @@ -5,7 +5,7 @@ function factory (type, config, load, typed) { var simplify = load(require('./simplify')); var equal = load(require('../relational/equal')); var isZero = load(require('../utils/isZero')); - var createNumericValue = load(require('./utils/createNumericValue')); + var numeric = load(require('../../type/numeric')); var ConstantNode = load(require('../../expression/node/ConstantNode')); var FunctionNode = load(require('../../expression/node/FunctionNode')); var OperatorNode = load(require('../../expression/node/OperatorNode')); @@ -740,7 +740,7 @@ function factory (type, config, load, typed) { * @return {ConstantNode} */ function createConstantNode(value, valueType) { - return new ConstantNode(createNumericValue(value, valueType || config.number)); + return new ConstantNode(numeric(value, valueType || config.number)); } return derivative; diff --git a/lib/function/algebra/utils/createNumericValue.js b/lib/function/algebra/utils/createNumericValue.js deleted file mode 100644 index d855f872a..000000000 --- a/lib/function/algebra/utils/createNumericValue.js +++ /dev/null @@ -1,31 +0,0 @@ -'use strict'; - -function factory(type, config, load, typed) { - - /** - * Create a numeric value with a specific type: number, BigNumber, or Fraction - * - * @param {string | number} value - * @param {'number' | 'BigNumber' | 'Fraction'} - * @return {number | BigNumber | Fraction} Returns an instance of the - * requested type - */ - return function createNumericValue (value, valueType) { - if (valueType === 'BigNumber') { - return new type.BigNumber(value); - } - - if (valueType === 'Fraction') { - return new type.Fraction(value); - } - - if (typeof value === 'number') { - return value; - } - else { - return parseFloat(value); - } - } -} - -exports.factory = factory; diff --git a/lib/type/numeric.js b/lib/type/numeric.js new file mode 100644 index 000000000..65e44fd10 --- /dev/null +++ b/lib/type/numeric.js @@ -0,0 +1,50 @@ +'use strict'; + +function factory(type, config, load, typed) { + + // TODO: expose this function to mathjs, add documentation + + /** + * Create a numeric value with a specific type: number, BigNumber, or Fraction + * + * @param {string | number} value + * @param {'number' | 'BigNumber' | 'Fraction'} + * @return {number | BigNumber | Fraction} Returns an instance of the + * numeric requested type + */ + return function numeric (value, valueType) { + if (valueType === 'BigNumber') { + return new type.BigNumber(value); + } + else if (valueType === 'Fraction') { + return new type.Fraction(value); + } + else { + // valueType === 'number' or undefined // TODO: check this + if (typeof value === 'number') { + return value; + } + else { + if (value === 'Infinity') { + return Infinity; + } + + if (value === 'NaN') { + return NaN; + } + + // The following regexp is relatively permissive + if (!/^[\-+]?((\d+\.?\d*)|(\d*\.?\d+))([eE][+\-]?\d+)?$/.test(value)) { + throw new Error('Invalid numeric value "' + value + '"'); + } + + // remove leading zeros like '003.2' which are not allowed by JavaScript + return parseFloat(value.replace(/^(0*)[0-9]/, function (match, zeros) { + return match.substring(zeros.length); + })); + } + } + } +} + +exports.factory = factory; diff --git a/test/expression/parse.test.js b/test/expression/parse.test.js index bff295e76..863910202 100644 --- a/test/expression/parse.test.js +++ b/test/expression/parse.test.js @@ -5,6 +5,7 @@ var math = require('../../index'); var ArgumentsError = require('../../lib/error/ArgumentsError'); var parse = math.expression.parse; var ConditionalNode = math.expression.node.ConditionalNode; +var ConstantNode = math.expression.node.ConstantNode; var OperatorNode = math.expression.node.OperatorNode; var RangeNode = math.expression.node.RangeNode; var Complex = math.type.Complex; @@ -368,10 +369,6 @@ describe('parse', function() { assert.ok(parseAndEval('5cm') instanceof Unit); }); - it('should parse constants', function() { - assert.equal(parseAndEval('pi'), Math.PI); - }); - it('should parse physical constants', function() { var expected = new Unit(299792458, 'm/s'); expected.fixPrefix = true; @@ -844,12 +841,35 @@ describe('parse', function() { describe('constants', function () { - it('should parse constants', function() { + it ('should parse symbolic constants', function () { + assert.strictEqual(parse('i').type, 'SymbolNode'); assert.deepEqual(parseAndEval('i'), new Complex(0, 1)); approx.equal(parseAndEval('pi'), Math.PI); approx.equal(parseAndEval('e'), Math.E); + }) + + it('should parse constants', function() { + assert.strictEqual(parse('true').type, 'ConstantNode'); + assert.deepStrictEqual(parse('true'), createConstantNode(true)); + assert.deepStrictEqual(parse('false'), createConstantNode(false)); + assert.deepStrictEqual(parse('null'), createConstantNode(null)); + assert.deepStrictEqual(parse('undefined'), createConstantNode(undefined)); + assert.deepStrictEqual(parse('uninitialized'), createConstantNode(math.uninitialized)); }); + it('should parse numeric constants', function() { + var nanConstantNode = parse('NaN'); + assert.deepStrictEqual(nanConstantNode.type, 'ConstantNode'); + assert.ok(isNaN(nanConstantNode.value)); + assert.deepStrictEqual(parse('Infinity'), createConstantNode(Infinity)); + }); + + // helper function to create a ConstantNode with empty comment + function createConstantNode (value) { + var c = new ConstantNode(value); + c.comment = '' + return c; + } }); describe('variables', function () { diff --git a/test/function/algebra/simplify.test.js b/test/function/algebra/simplify.test.js index 27d5700bf..b89ac2e04 100644 --- a/test/function/algebra/simplify.test.js +++ b/test/function/algebra/simplify.test.js @@ -260,15 +260,16 @@ describe('simplify', function() { assert.equal(math.simplify('LN10', ['LN10 -> 1']).toString(), '1'); assert.equal(math.simplify('LOG2E', ['LOG2E -> 1']).toString(), '1'); assert.equal(math.simplify('LOG10E', ['LOG10E -> 1']).toString(), '1'); - assert.equal(math.simplify('NaN', ['NaN -> 1']).toString(), '1'); + assert.equal(math.simplify('null', ['null -> 1']).toString(), '1'); assert.equal(math.simplify('phi', ['phi -> 1']).toString(), '1'); assert.equal(math.simplify('SQRT1_2', ['SQRT1_2 -> 1']).toString(), '1'); assert.equal(math.simplify('SQRT2', ['SQRT2 -> 1']).toString(), '1'); assert.equal(math.simplify('tau', ['tau -> 1']).toString(), '1'); + + // note that NaN is a special case, we can't compare two values both NaN. }); it('should throw an error for invalid built-in constant symbols in rules', function() { - assert.throws(function(){ math.simplify('null', ['null -> 1']).toString(); }); assert.throws(function(){ math.simplify('uninitialized', ['uninitialized -> 1']).toString(); }); assert.throws(function(){ math.simplify('version', ['version -> 1']).toString(); }); }); From f112348eec9c786f9cb1f169c170936c3dfb8f4d Mon Sep 17 00:00:00 2001 From: jos Date: Thu, 25 Jan 2018 20:07:07 +0100 Subject: [PATCH 3/6] Fixed using the wrong typeof function which was not aware of mathjs types like BigNumber (see #1023) --- lib/expression/node/ConstantNode.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/expression/node/ConstantNode.js b/lib/expression/node/ConstantNode.js index db0f5ebf0..f4a6dbae2 100644 --- a/lib/expression/node/ConstantNode.js +++ b/lib/expression/node/ConstantNode.js @@ -1,10 +1,10 @@ 'use strict'; -var getType = require('../../utils/types').type; var format = require('../../utils/string').format; function factory (type, config, load, typed) { var Node = load(require('./Node')); + var getType = load(require('../../function/utils/typeof')); /** * A ConstantNode holds a constant value like a number or string. From 4a7e956242a8ea47f577792a660aece6f8b71a66 Mon Sep 17 00:00:00 2001 From: Max Bruckner Date: Thu, 25 Jan 2018 20:14:06 +0100 Subject: [PATCH 4/6] ConstantNode: Add Fraction specific latex output. --- lib/expression/node/ConstantNode.js | 4 +++- test/expression/node/ConstantNode.test.js | 9 +++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/expression/node/ConstantNode.js b/lib/expression/node/ConstantNode.js index f4a6dbae2..a4c6e2920 100644 --- a/lib/expression/node/ConstantNode.js +++ b/lib/expression/node/ConstantNode.js @@ -134,7 +134,6 @@ function factory (type, config, load, typed) { case 'number': case 'BigNumber': - case 'Fraction': var index = value.toLowerCase().indexOf('e'); if (index !== -1) { return value.substring(0, index) + '\\cdot10^{' + @@ -142,6 +141,9 @@ function factory (type, config, load, typed) { } return value; + case 'Fraction': + return this.value.toLatex(); + default: return value; } diff --git a/test/expression/node/ConstantNode.test.js b/test/expression/node/ConstantNode.test.js index 8bca81aed..53b812d8c 100644 --- a/test/expression/node/ConstantNode.test.js +++ b/test/expression/node/ConstantNode.test.js @@ -6,6 +6,7 @@ var bigmath = require('../../../index').create({number: 'BigNumber'}); var Node = math.expression.node.Node; var ConstantNode = math.expression.node.ConstantNode; var SymbolNode = math.expression.node.SymbolNode; +var Fraction = require('../../../lib/type/fraction/Fraction'); describe('ConstantNode', function() { @@ -184,4 +185,12 @@ describe('ConstantNode', function() { assert.equal(n.toTex({handler: customFunction}), 'const\\left(1\\right)'); }); + it ('should LaTeX a ConstantNode with a fraction', function () { + var positive = new ConstantNode(new math.type.Fraction(1.5)); + var negative = new ConstantNode(new math.type.Fraction(-1.5)); + + assert.equal(positive.toTex(), '\\frac{3}{2}'); + assert.equal(negative.toTex(), '-\\frac{3}{2}'); + }); + }); From 02d7d592e4198f43e5016c2b4224a231947480bc Mon Sep 17 00:00:00 2001 From: jos Date: Thu, 25 Jan 2018 20:21:22 +0100 Subject: [PATCH 5/6] Merged util function `types.type` into `math.typeof` (see #1023) --- lib/function/utils/typeof.js | 24 ++++++++++---- lib/utils/array.js | 2 -- lib/utils/index.js | 1 - lib/utils/types.js | 44 ------------------------- test/function/utils/typeof.test.js | 13 +++++++- test/utils/types.test.js | 53 ------------------------------ 6 files changed, 29 insertions(+), 108 deletions(-) delete mode 100644 lib/utils/types.js delete mode 100644 test/utils/types.test.js diff --git a/lib/function/utils/typeof.js b/lib/function/utils/typeof.js index ef9b30550..fde4b4e84 100644 --- a/lib/function/utils/typeof.js +++ b/lib/function/utils/typeof.js @@ -1,7 +1,5 @@ 'use strict'; -var types = require('../../utils/types'); - function factory (type, config, load, typed) { /** * Determine the type of a variable. @@ -48,11 +46,19 @@ function factory (type, config, load, typed) { */ var _typeof = typed('_typeof', { 'any': function (x) { - // JavaScript types - var t = types.type(x); + var t = typeof x; - // math.js types - if (t === 'Object') { + if (t === 'object') { + // JavaScript types + if (x === null) return 'null'; + if (Array.isArray(x)) return 'Array'; + if (x instanceof Date) return 'Date'; + if (x instanceof RegExp) return 'RegExp'; + if (x instanceof Boolean) return 'boolean'; + if (x instanceof Number) return 'number'; + if (x instanceof String) return 'string'; + + // math.js types if (type.isBigNumber(x)) return 'BigNumber'; if (type.isComplex(x)) return 'Complex'; if (type.isFraction(x)) return 'Fraction'; @@ -62,9 +68,13 @@ function factory (type, config, load, typed) { if (type.isRange(x)) return 'Range'; if (type.isChain(x)) return 'Chain'; if (type.isHelp(x)) return 'Help'; + + return 'Object'; } - return t; + if (t === 'function') return 'Function'; + + return t; // can be 'string', 'number', 'boolean', ... } }); diff --git a/lib/utils/array.js b/lib/utils/array.js index 8c9171290..ac527b78d 100644 --- a/lib/utils/array.js +++ b/lib/utils/array.js @@ -2,8 +2,6 @@ var number = require('./number'); var string = require('./string'); -var object = require('./object'); -var types = require('./types'); var DimensionError = require('../error/DimensionError'); var IndexError = require('../error/IndexError'); diff --git a/lib/utils/index.js b/lib/utils/index.js index a28d1565a..266681eb4 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -6,5 +6,4 @@ exports['function'] = require('./function'); exports.number = require('./number'); exports.object = require('./object'); exports.string = require('./string'); -exports.types = require('./types'); exports.emitter = require('./emitter'); diff --git a/lib/utils/types.js b/lib/utils/types.js deleted file mode 100644 index 2154fd62b..000000000 --- a/lib/utils/types.js +++ /dev/null @@ -1,44 +0,0 @@ -'use strict'; - -/** - * Determine the type of a variable - * - * type(x) - * - * The following types are recognized: - * - * 'undefined' - * 'null' - * 'boolean' - * 'number' - * 'string' - * 'Array' - * 'Function' - * 'Date' - * 'RegExp' - * 'Object' - * - * @param {*} x - * @return {string} Returns the name of the type. Primitive types are lower case, - * non-primitive types are upper-camel-case. - * For example 'number', 'string', 'Array', 'Date'. - */ -exports.type = function(x) { - var type = typeof x; - - if (type === 'object') { - if (x === null) return 'null'; - if (Array.isArray(x)) return 'Array'; - if (x instanceof Date) return 'Date'; - if (x instanceof RegExp) return 'RegExp'; - if (x instanceof Boolean) return 'boolean'; - if (x instanceof Number) return 'number'; - if (x instanceof String) return 'string'; - - return 'Object'; - } - - if (type === 'function') return 'Function'; - - return type; -}; diff --git a/test/function/utils/typeof.test.js b/test/function/utils/typeof.test.js index ea46fa29b..55b551376 100644 --- a/test/function/utils/typeof.test.js +++ b/test/function/utils/typeof.test.js @@ -14,6 +14,8 @@ describe('typeof', function() { it('should return number type for a number', function() { assert.equal(math.typeof(2), 'number'); assert.equal(math.typeof(new Number(2)), 'number'); + assert.equal(math.typeof(new Number(2.3)), 'number'); + assert.equal(math.typeof(NaN), 'number'); }); it('should return bignumber type for a bignumber', function() { @@ -66,7 +68,7 @@ describe('typeof', function() { assert.equal(math.typeof(null), 'null'); }); - it('should return undefined type for undefined', function() { + it('should return undefined type for undefined', function() { assert.equal(math.typeof(undefined), 'undefined'); }); @@ -74,6 +76,10 @@ describe('typeof', function() { assert.equal(math.typeof(new Date()), 'Date'); }); + it('should return the type of a regexp', function () { + assert.equal(math.typeof(/regexp/), 'RegExp'); + }); + it('should return function type for a function', function() { assert.equal(math.typeof(function () {}), 'Function'); assert.equal(math.typeof(new Function ()), 'Function'); @@ -110,4 +116,9 @@ describe('typeof', function() { assert.equal(expression.toTex(), '\\mathrm{typeof}\\left(1\\right)'); }); + it('should throw an error in case of wrong number of arguments', function () { + assert.throws(function () {math.typeof()}, /Too few arguments in function _typeof/); + assert.throws(function () {math.typeof(1,2,3)}, /Too many arguments in function _typeof/); + }) + }); diff --git a/test/utils/types.test.js b/test/utils/types.test.js deleted file mode 100644 index c826c6636..000000000 --- a/test/utils/types.test.js +++ /dev/null @@ -1,53 +0,0 @@ -// test types utils -var assert = require('assert'), - approx = require('../../tools/approx'), - types = require('../../lib/utils/types'); - -describe ('types', function () { - - it('should return the type of undefined', function () { - assert.equal(types.type(undefined), 'undefined'); - assert.equal(types.type(), 'undefined'); - }); - - it('should return the type of a boolean', function () { - - assert.equal(types.type(false), 'boolean'); - assert.equal(types.type(true), 'boolean'); - }); - - it('should return the type of a number', function () { - assert.equal(types.type(2.3), 'number'); - assert.equal(types.type(Number(2.3)), 'number'); - assert.equal(types.type(new Number(2.3)), 'number'); - assert.equal(types.type(NaN), 'number'); - }); - - it('should return the type of a string', function () { - assert.equal(types.type('bla'), 'string'); - assert.equal(types.type(new String('bla')), 'string'); - }); - - it('should return the type of an object', function () { - assert.equal(types.type({}), 'Object'); - assert.equal(types.type(new Object()), 'Object'); - }); - - it('should return the type of an array', function () { - assert.equal(types.type([]), 'Array'); - assert.equal(types.type(new Array()), 'Array'); - }); - - it('should return the type of a function', function () { - assert.equal(types.type(function () {}), 'Function'); - }); - - it('should return the type of a date', function () { - assert.equal(types.type(new Date()), 'Date'); - }); - - it('should return the type of a regexp', function () { - assert.equal(types.type(/regexp/), 'RegExp'); - }); - -}); \ No newline at end of file From 4fcc9d231a279c181f715aa6293f6a288181c149 Mon Sep 17 00:00:00 2001 From: jos Date: Thu, 25 Jan 2018 20:29:43 +0100 Subject: [PATCH 6/6] Added a few more unit tests for ConstantNode --- test/expression/node/ConstantNode.test.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/expression/node/ConstantNode.test.js b/test/expression/node/ConstantNode.test.js index 53b812d8c..71dbb8ebb 100644 --- a/test/expression/node/ConstantNode.test.js +++ b/test/expression/node/ConstantNode.test.js @@ -137,6 +137,8 @@ describe('ConstantNode', function() { it ('should stringify a ConstantNode', function () { assert.equal(new ConstantNode(3).toString(), '3'); assert.deepEqual(new ConstantNode(3).toString(), '3'); + assert.deepEqual(new ConstantNode(math.bignumber('1e500')).toString(), '1e+500'); + assert.deepEqual(new ConstantNode(math.fraction(2,3)).toString(), '2/3'); assert.equal(new ConstantNode('hi').toString(), '"hi"'); assert.equal(new ConstantNode(true).toString(), 'true'); assert.equal(new ConstantNode(false).toString(), 'false'); @@ -160,6 +162,7 @@ describe('ConstantNode', function() { it ('should LaTeX a ConstantNode', function () { assert.equal(new ConstantNode(3).toTex(), '3'); assert.deepEqual(new ConstantNode(3).toTex(), '3'); + assert.deepEqual(new ConstantNode(math.bignumber('3')).toTex(), '3'); assert.equal(new ConstantNode('hi').toTex(), '\\mathtt{"hi"}'); assert.equal(new ConstantNode(true).toTex(), 'true'); assert.equal(new ConstantNode(false).toTex(), 'false');