From 76908457ff4c56e7ce27dcdb0e9aafec008f72be Mon Sep 17 00:00:00 2001 From: Matt Vague Date: Mon, 12 Sep 2022 13:07:05 -0700 Subject: [PATCH 01/17] Implement fromJSON --- src/factoriesAny.js | 1 + src/json/fromJSON.js | 18 ++++++++++++++++ test/unit-tests/json/fromJSON.test.js | 31 +++++++++++++++++++++++++++ 3 files changed, 50 insertions(+) create mode 100644 src/json/fromJSON.js create mode 100644 test/unit-tests/json/fromJSON.test.js diff --git a/src/factoriesAny.js b/src/factoriesAny.js index 2ffc291c23..747c9b2ccc 100644 --- a/src/factoriesAny.js +++ b/src/factoriesAny.js @@ -256,6 +256,7 @@ export { createDerivative } from './function/algebra/derivative.js' export { createRationalize } from './function/algebra/rationalize.js' export { createReviver } from './json/reviver.js' export { createReplacer } from './json/replacer.js' +export { createfromJSON } from './json/fromJSON.js' export { createE, createUppercaseE, diff --git a/src/json/fromJSON.js b/src/json/fromJSON.js new file mode 100644 index 0000000000..e12531191b --- /dev/null +++ b/src/json/fromJSON.js @@ -0,0 +1,18 @@ +import { factory } from '../utils/factory.js' + +const name = 'fromJSON' +const dependencies = [ + 'classes' +] + +export const createfromJSON = /* #__PURE__ */ factory(name, dependencies, ({ classes }) => { + return function fromJSON (json) { + const constructor = classes[json && json.mathjs] + + if (constructor && typeof constructor.fromJSON === 'function') { + return constructor.fromJSON(json) + } + + return json + } +}) diff --git a/test/unit-tests/json/fromJSON.test.js b/test/unit-tests/json/fromJSON.test.js new file mode 100644 index 0000000000..d7da1fbd8e --- /dev/null +++ b/test/unit-tests/json/fromJSON.test.js @@ -0,0 +1,31 @@ +import assert from 'assert' +import math from '../../../src/defaultInstance.js' +const fromJSON = math.fromJSON + +describe('toObject', function () { + it('output JSON for a node', function () { + const node = new math.OperatorNode('+', 'add', [ + new math.SymbolNode('x'), + new math.ConstantNode(2) + ]) + + assert.deepStrictEqual( + fromJSON({ + args: [ + { + name: 'x' + }, + { + value: 2 + } + ], + fn: 'add', + implicit: false, + isPercentage: false, + mathjs: 'OperatorNode', + op: '+' + }), + node + ) + }) +}) From eaee338a5b55011727eb9a8b265364c154f77be4 Mon Sep 17 00:00:00 2001 From: Matt Vague Date: Mon, 12 Sep 2022 13:07:19 -0700 Subject: [PATCH 02/17] Fix OperatorNode serialization --- src/expression/node/OperatorNode.js | 10 ++++++---- test/unit-tests/expression/node/OperatorNode.test.js | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/expression/node/OperatorNode.js b/src/expression/node/OperatorNode.js index 21eeeb744c..363d06c4eb 100644 --- a/src/expression/node/OperatorNode.js +++ b/src/expression/node/OperatorNode.js @@ -8,10 +8,11 @@ import { factory } from '../../utils/factory.js' const name = 'OperatorNode' const dependencies = [ - 'Node' + 'Node', + 'fromJSON' ] -export const createOperatorNode = /* #__PURE__ */ factory(name, dependencies, ({ Node }) => { +export const createOperatorNode = /* #__PURE__ */ factory(name, dependencies, ({ Node, fromJSON }) => { /** * Returns true if the expression starts with a constant, under * the current parenthesization: @@ -463,7 +464,7 @@ export const createOperatorNode = /* #__PURE__ */ factory(name, dependencies, ({ mathjs: name, op: this.op, fn: this.fn, - args: this.args, + args: (this.args || []).map(a => a.toJSON()), implicit: this.implicit, isPercentage: this.isPercentage } @@ -484,7 +485,8 @@ export const createOperatorNode = /* #__PURE__ */ factory(name, dependencies, ({ */ static fromJSON (json) { return new OperatorNode( - json.op, json.fn, json.args, json.implicit, json.isPercentage) + json.op, json.fn, (json.args || []).map(fromJSON), json.implicit, json.isPercentage + ) } /** diff --git a/test/unit-tests/expression/node/OperatorNode.test.js b/test/unit-tests/expression/node/OperatorNode.test.js index 11de90715f..606dbb5902 100644 --- a/test/unit-tests/expression/node/OperatorNode.test.js +++ b/test/unit-tests/expression/node/OperatorNode.test.js @@ -589,7 +589,7 @@ describe('OperatorNode', function () { mathjs: 'OperatorNode', op: '+', fn: 'add', - args: [one, two], + args: [one.toJSON(), two.toJSON()], implicit: true, isPercentage: false }) From 8ada70a045fbe349085708c21bc359f76e0dfb35 Mon Sep 17 00:00:00 2001 From: Matt Vague Date: Mon, 12 Sep 2022 13:07:27 -0700 Subject: [PATCH 03/17] Fix ArrayNode serialization --- src/expression/node/ArrayNode.js | 9 +++++---- test/unit-tests/expression/node/ArrayNode.test.js | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/expression/node/ArrayNode.js b/src/expression/node/ArrayNode.js index 800e536029..f1820c019d 100644 --- a/src/expression/node/ArrayNode.js +++ b/src/expression/node/ArrayNode.js @@ -4,10 +4,11 @@ import { factory } from '../../utils/factory.js' const name = 'ArrayNode' const dependencies = [ - 'Node' + 'Node', + 'fromJSON' ] -export const createArrayNode = /* #__PURE__ */ factory(name, dependencies, ({ Node }) => { +export const createArrayNode = /* #__PURE__ */ factory(name, dependencies, ({ Node, fromJSON }) => { class ArrayNode extends Node { /** * @constructor ArrayNode @@ -117,7 +118,7 @@ export const createArrayNode = /* #__PURE__ */ factory(name, dependencies, ({ No toJSON () { return { mathjs: name, - items: this.items + items: this.items.map(i => i.toJSON()) } } @@ -129,7 +130,7 @@ export const createArrayNode = /* #__PURE__ */ factory(name, dependencies, ({ No * @returns {ArrayNode} */ static fromJSON (json) { - return new ArrayNode(json.items) + return new ArrayNode(json.items.map(fromJSON)) } /** diff --git a/test/unit-tests/expression/node/ArrayNode.test.js b/test/unit-tests/expression/node/ArrayNode.test.js index c27f55cb52..dcadc87c1a 100644 --- a/test/unit-tests/expression/node/ArrayNode.test.js +++ b/test/unit-tests/expression/node/ArrayNode.test.js @@ -284,7 +284,7 @@ describe('ArrayNode', function () { assert.deepStrictEqual(json, { mathjs: 'ArrayNode', - items: [b, c] + items: [b.toJSON(), c.toJSON()] }) const parsed = ArrayNode.fromJSON(json) From 299f2db64d8b74b2edd8456a4d0be57c4c55917e Mon Sep 17 00:00:00 2001 From: Matt Vague Date: Mon, 12 Sep 2022 13:11:20 -0700 Subject: [PATCH 04/17] Use optional chaining --- src/expression/node/ArrayNode.js | 2 +- src/expression/node/AssignmentNode.js | 13 +++++++------ src/expression/node/OperatorNode.js | 2 +- .../expression/node/AssignmentNode.test.js | 6 +++--- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/expression/node/ArrayNode.js b/src/expression/node/ArrayNode.js index f1820c019d..783af6471e 100644 --- a/src/expression/node/ArrayNode.js +++ b/src/expression/node/ArrayNode.js @@ -118,7 +118,7 @@ export const createArrayNode = /* #__PURE__ */ factory(name, dependencies, ({ No toJSON () { return { mathjs: name, - items: this.items.map(i => i.toJSON()) + items: this.items?.map(i => i.toJSON()) } } diff --git a/src/expression/node/AssignmentNode.js b/src/expression/node/AssignmentNode.js index 87805f110f..f11a1578f1 100644 --- a/src/expression/node/AssignmentNode.js +++ b/src/expression/node/AssignmentNode.js @@ -9,10 +9,11 @@ const name = 'AssignmentNode' const dependencies = [ 'subset', '?matrix', // FIXME: should not be needed at all, should be handled by subset - 'Node' + 'Node', + 'fromJSON' ] -export const createAssignmentNode = /* #__PURE__ */ factory(name, dependencies, ({ subset, matrix, Node }) => { +export const createAssignmentNode = /* #__PURE__ */ factory(name, dependencies, ({ subset, matrix, Node, fromJSON }) => { const access = accessFactory({ subset }) const assign = assignFactory({ subset, matrix }) @@ -258,9 +259,9 @@ export const createAssignmentNode = /* #__PURE__ */ factory(name, dependencies, toJSON () { return { mathjs: name, - object: this.object, - index: this.index, - value: this.value + object: this.object?.toJSON(), + index: this.index?.toJSON(), + value: this.value?.toJSON() } } @@ -273,7 +274,7 @@ export const createAssignmentNode = /* #__PURE__ */ factory(name, dependencies, * @returns {AssignmentNode} */ static fromJSON (json) { - return new AssignmentNode(json.object, json.index, json.value) + return new AssignmentNode(fromJSON(json.object), fromJSON(json.index), fromJSON(json.value)) } /** diff --git a/src/expression/node/OperatorNode.js b/src/expression/node/OperatorNode.js index 363d06c4eb..915af1d409 100644 --- a/src/expression/node/OperatorNode.js +++ b/src/expression/node/OperatorNode.js @@ -485,7 +485,7 @@ export const createOperatorNode = /* #__PURE__ */ factory(name, dependencies, ({ */ static fromJSON (json) { return new OperatorNode( - json.op, json.fn, (json.args || []).map(fromJSON), json.implicit, json.isPercentage + json.op, json.fn, json.args?.map(fromJSON), json.implicit, json.isPercentage ) } diff --git a/test/unit-tests/expression/node/AssignmentNode.test.js b/test/unit-tests/expression/node/AssignmentNode.test.js index b48a26bd45..49631fbe76 100644 --- a/test/unit-tests/expression/node/AssignmentNode.test.js +++ b/test/unit-tests/expression/node/AssignmentNode.test.js @@ -519,9 +519,9 @@ describe('AssignmentNode', function () { assert.deepStrictEqual(json, { mathjs: 'AssignmentNode', - index: node.index, - object: a, - value: d + index: node.index.toJSON(), + object: a.toJSON(), + value: d.toJSON() }) const parsed = AssignmentNode.fromJSON(json) From ece2a561d9d5b0746df48086e8e8a17751c2241e Mon Sep 17 00:00:00 2001 From: Matt Vague Date: Mon, 12 Sep 2022 13:15:26 -0700 Subject: [PATCH 05/17] Fix BlockNode serialization --- src/expression/node/BlockNode.js | 336 +++++++++--------- .../expression/node/BlockNode.test.js | 2 +- 2 files changed, 178 insertions(+), 160 deletions(-) diff --git a/src/expression/node/BlockNode.js b/src/expression/node/BlockNode.js index 75b041d916..ed936f9a1e 100644 --- a/src/expression/node/BlockNode.js +++ b/src/expression/node/BlockNode.js @@ -3,184 +3,202 @@ import { forEach, map } from '../../utils/array.js' import { factory } from '../../utils/factory.js' const name = 'BlockNode' -const dependencies = [ - 'ResultSet', - 'Node' -] - -export const createBlockNode = /* #__PURE__ */ factory(name, dependencies, ({ ResultSet, Node }) => { - class BlockNode extends Node { - /** - * @constructor BlockNode - * @extends {Node} - * Holds a set with blocks - * @param {Array.<{node: Node} | {node: Node, visible: boolean}>} blocks - * An array with blocks, where a block is constructed as an - * Object with properties block, which is a Node, and visible, - * which is a boolean. The property visible is optional and - * is true by default - */ - constructor (blocks) { - super() - // validate input, copy blocks - if (!Array.isArray(blocks)) throw new Error('Array expected') - this.blocks = blocks.map(function (block) { - const node = block && block.node - const visible = block && - block.visible !== undefined - ? block.visible - : true - - if (!isNode(node)) throw new TypeError('Property "node" must be a Node') - if (typeof visible !== 'boolean') { throw new TypeError('Property "visible" must be a boolean') } - - return { node, visible } - }) - } +const dependencies = ['ResultSet', 'Node', 'fromJSON'] + +export const createBlockNode = /* #__PURE__ */ factory( + name, + dependencies, + ({ ResultSet, Node, fromJSON }) => { + class BlockNode extends Node { + /** + * @constructor BlockNode + * @extends {Node} + * Holds a set with blocks + * @param {Array.<{node: Node} | {node: Node, visible: boolean}>} blocks + * An array with blocks, where a block is constructed as an + * Object with properties block, which is a Node, and visible, + * which is a boolean. The property visible is optional and + * is true by default + */ + constructor (blocks) { + super() + // validate input, copy blocks + if (!Array.isArray(blocks)) throw new Error('Array expected') + this.blocks = blocks.map(function (block) { + const node = block && block.node + const visible = + block && block.visible !== undefined ? block.visible : true + + if (!isNode(node)) { throw new TypeError('Property "node" must be a Node') } + if (typeof visible !== 'boolean') { + throw new TypeError('Property "visible" must be a boolean') + } - static name = name - get type () { return name } - get isBlockNode () { return true } - - /** - * 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: *) - */ - _compile (math, argNames) { - const evalBlocks = map(this.blocks, function (block) { - return { - evaluate: block.node._compile(math, argNames), - visible: block.visible - } - }) + return { node, visible } + }) + } + + static name = name + get type () { + return name + } - return function evalBlockNodes (scope, args, context) { - const results = [] + get isBlockNode () { + return true + } - forEach(evalBlocks, function evalBlockNode (block) { - const result = block.evaluate(scope, args, context) - if (block.visible) { - results.push(result) + /** + * 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: *) + */ + _compile (math, argNames) { + const evalBlocks = map(this.blocks, function (block) { + return { + evaluate: block.node._compile(math, argNames), + visible: block.visible } }) - return new ResultSet(results) - } - } + return function evalBlockNodes (scope, args, context) { + const results = [] - /** - * Execute a callback for each of the child blocks of this node - * @param {function(child: Node, path: string, parent: Node)} callback - */ - forEach (callback) { - for (let i = 0; i < this.blocks.length; i++) { - callback(this.blocks[i].node, 'blocks[' + i + '].node', this) + forEach(evalBlocks, function evalBlockNode (block) { + const result = block.evaluate(scope, args, context) + if (block.visible) { + results.push(result) + } + }) + + return new ResultSet(results) + } } - } - /** - * Create a new BlockNode whose children are the results of calling - * the provided callback function for each child of the original node. - * @param {function(child: Node, path: string, parent: Node): Node} callback - * @returns {BlockNode} Returns a transformed copy of the node - */ - map (callback) { - const blocks = [] - for (let i = 0; i < this.blocks.length; i++) { - const block = this.blocks[i] - const node = this._ifNode( - callback(block.node, 'blocks[' + i + '].node', this)) - blocks[i] = { - node, - visible: block.visible + /** + * Execute a callback for each of the child blocks of this node + * @param {function(child: Node, path: string, parent: Node)} callback + */ + forEach (callback) { + for (let i = 0; i < this.blocks.length; i++) { + callback(this.blocks[i].node, 'blocks[' + i + '].node', this) } } - return new BlockNode(blocks) - } - /** - * Create a clone of this node, a shallow copy - * @return {BlockNode} - */ - clone () { - const blocks = this.blocks.map(function (block) { - return { - node: block.node, - visible: block.visible + /** + * Create a new BlockNode whose children are the results of calling + * the provided callback function for each child of the original node. + * @param {function(child: Node, path: string, parent: Node): Node} callback + * @returns {BlockNode} Returns a transformed copy of the node + */ + map (callback) { + const blocks = [] + for (let i = 0; i < this.blocks.length; i++) { + const block = this.blocks[i] + const node = this._ifNode( + callback(block.node, 'blocks[' + i + '].node', this) + ) + blocks[i] = { + node, + visible: block.visible + } } - }) + return new BlockNode(blocks) + } - return new BlockNode(blocks) - } + /** + * Create a clone of this node, a shallow copy + * @return {BlockNode} + */ + clone () { + const blocks = this.blocks.map(function (block) { + return { + node: block.node, + visible: block.visible + } + }) - /** - * Get string representation - * @param {Object} options - * @return {string} str - * @override - */ - _toString (options) { - return this.blocks.map(function (param) { - return param.node.toString(options) + (param.visible ? '' : ';') - }).join('\n') - } + return new BlockNode(blocks) + } - /** - * Get a JSON representation of the node - * @returns {Object} - */ - toJSON () { - return { - mathjs: name, - blocks: this.blocks + /** + * Get string representation + * @param {Object} options + * @return {string} str + * @override + */ + _toString (options) { + return this.blocks + .map(function (param) { + return param.node.toString(options) + (param.visible ? '' : ';') + }) + .join('\n') } - } - /** - * Instantiate an BlockNode from its JSON representation - * @param {Object} json - * An object structured like - * `{"mathjs": "BlockNode", blocks: [{node: ..., visible: false}, ...]}`, - * where mathjs is optional - * @returns {BlockNode} - */ - static fromJSON (json) { - return new BlockNode(json.blocks) - } + /** + * Get a JSON representation of the node + * @returns {Object} + */ + toJSON () { + return { + mathjs: name, + blocks: this.blocks.map((b) => ({ ...b, node: b.node.toJSON() })) + } + } - /** - * Get HTML representation - * @param {Object} options - * @return {string} str - * @override - */ - toHTML (options) { - return this.blocks.map(function (param) { - return param.node.toHTML(options) + - (param.visible ? '' : ';') - }).join('
') - } + /** + * Instantiate an BlockNode from its JSON representation + * @param {Object} json + * An object structured like + * `{"mathjs": "BlockNode", blocks: [{node: ..., visible: false}, ...]}`, + * where mathjs is optional + * @returns {BlockNode} + */ + static fromJSON (json) { + return new BlockNode( + json.blocks.map((b) => ({ ...b, node: fromJSON(b.node) })) + ) + } + + /** + * Get HTML representation + * @param {Object} options + * @return {string} str + * @override + */ + toHTML (options) { + return this.blocks + .map(function (param) { + return ( + param.node.toHTML(options) + + (param.visible ? '' : ';') + ) + }) + .join('
') + } - /** - * Get LaTeX representation - * @param {Object} options - * @return {string} str - */ - _toTex (options) { - return this.blocks.map(function (param) { - return param.node.toTex(options) + (param.visible ? '' : ';') - }).join('\\;\\;\n') + /** + * Get LaTeX representation + * @param {Object} options + * @return {string} str + */ + _toTex (options) { + return this.blocks + .map(function (param) { + return param.node.toTex(options) + (param.visible ? '' : ';') + }) + .join('\\;\\;\n') + } } - } - return BlockNode -}, { isClass: true, isNode: true }) + return BlockNode + }, + { isClass: true, isNode: true } +) diff --git a/test/unit-tests/expression/node/BlockNode.test.js b/test/unit-tests/expression/node/BlockNode.test.js index f856f1332c..ffcfec1db0 100644 --- a/test/unit-tests/expression/node/BlockNode.test.js +++ b/test/unit-tests/expression/node/BlockNode.test.js @@ -304,7 +304,7 @@ describe('BlockNode', function () { assert.deepStrictEqual(json, { mathjs: 'BlockNode', - blocks: [bBlock, cBlock] + blocks: [{ node: b.toJSON(), visible: false }, { node: c.toJSON(), visible: true }] }) const parsed = BlockNode.fromJSON(json) From d173a958365c0ce48dea07b77cc67d31c4c05e07 Mon Sep 17 00:00:00 2001 From: Matt Vague Date: Mon, 12 Sep 2022 13:17:05 -0700 Subject: [PATCH 06/17] Fix serialization for ConditionalNode --- src/expression/node/ConditionalNode.js | 13 +++++++------ .../expression/node/ConditionalNode.test.js | 6 +++--- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/expression/node/ConditionalNode.js b/src/expression/node/ConditionalNode.js index 121f4df6b4..c7abc8322f 100644 --- a/src/expression/node/ConditionalNode.js +++ b/src/expression/node/ConditionalNode.js @@ -4,10 +4,11 @@ import { getPrecedence } from '../operators.js' const name = 'ConditionalNode' const dependencies = [ - 'Node' + 'Node', + 'fromJSON' ] -export const createConditionalNode = /* #__PURE__ */ factory(name, dependencies, ({ Node }) => { +export const createConditionalNode = /* #__PURE__ */ factory(name, dependencies, ({ Node, fromJSON }) => { /** * Test whether a condition is met * @param {*} condition @@ -176,9 +177,9 @@ export const createConditionalNode = /* #__PURE__ */ factory(name, dependencies, toJSON () { return { mathjs: name, - condition: this.condition, - trueExpr: this.trueExpr, - falseExpr: this.falseExpr + condition: this.condition?.toJSON(), + trueExpr: this.trueExpr?.toJSON(), + falseExpr: this.falseExpr?.toJSON() } } @@ -196,7 +197,7 @@ export const createConditionalNode = /* #__PURE__ */ factory(name, dependencies, * @returns {ConditionalNode} */ static fromJSON (json) { - return new ConditionalNode(json.condition, json.trueExpr, json.falseExpr) + return new ConditionalNode(fromJSON(json.condition), fromJSON(json.trueExpr), fromJSON(json.falseExpr)) } /** diff --git a/test/unit-tests/expression/node/ConditionalNode.test.js b/test/unit-tests/expression/node/ConditionalNode.test.js index f8cd321989..54dffc36e2 100644 --- a/test/unit-tests/expression/node/ConditionalNode.test.js +++ b/test/unit-tests/expression/node/ConditionalNode.test.js @@ -310,9 +310,9 @@ describe('ConditionalNode', function () { assert.deepStrictEqual(json, { mathjs: 'ConditionalNode', - condition: a, - trueExpr: b, - falseExpr: c + condition: a.toJSON(), + trueExpr: b.toJSON(), + falseExpr: c.toJSON() }) const parsed = ConditionalNode.fromJSON(json) From b60924bd444ff20a5c3df4085f63a335854a0098 Mon Sep 17 00:00:00 2001 From: Matt Vague Date: Mon, 12 Sep 2022 13:19:02 -0700 Subject: [PATCH 07/17] Fix FunctionAssignmentNode serialization --- src/expression/node/FunctionAssignmentNode.js | 9 +++++---- .../expression/node/FunctionAssignmentNode.test.js | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/expression/node/FunctionAssignmentNode.js b/src/expression/node/FunctionAssignmentNode.js index 4adce75daf..21b4c0381b 100644 --- a/src/expression/node/FunctionAssignmentNode.js +++ b/src/expression/node/FunctionAssignmentNode.js @@ -10,10 +10,11 @@ import { factory } from '../../utils/factory.js' const name = 'FunctionAssignmentNode' const dependencies = [ 'typed', - 'Node' + 'Node', + 'fromJSON' ] -export const createFunctionAssignmentNode = /* #__PURE__ */ factory(name, dependencies, ({ typed, Node }) => { +export const createFunctionAssignmentNode = /* #__PURE__ */ factory(name, dependencies, ({ typed, Node, fromJSON }) => { /** * Is parenthesis needed? * @param {Node} node @@ -174,7 +175,7 @@ export const createFunctionAssignmentNode = /* #__PURE__ */ factory(name, depend type: types[index] } }), - expr: this.expr + expr: this.expr?.toJSON() } } @@ -190,7 +191,7 @@ export const createFunctionAssignmentNode = /* #__PURE__ */ factory(name, depend * @returns {FunctionAssignmentNode} */ static fromJSON (json) { - return new FunctionAssignmentNode(json.name, json.params, json.expr) + return new FunctionAssignmentNode(json.name, json.params, fromJSON(json.expr)) } /** diff --git a/test/unit-tests/expression/node/FunctionAssignmentNode.test.js b/test/unit-tests/expression/node/FunctionAssignmentNode.test.js index 35f6f5ece2..e9dc6e6533 100644 --- a/test/unit-tests/expression/node/FunctionAssignmentNode.test.js +++ b/test/unit-tests/expression/node/FunctionAssignmentNode.test.js @@ -434,7 +434,7 @@ describe('FunctionAssignmentNode', function () { { name: 'x', type: 'number' }, { name: 'y', type: 'any' } ], - expr + expr: expr.toJSON() }) const parsed = FunctionAssignmentNode.fromJSON(json) From 1cc0dc1d2f3224b4122f308ac30c888a94ec9286 Mon Sep 17 00:00:00 2001 From: Matt Vague Date: Mon, 12 Sep 2022 13:20:33 -0700 Subject: [PATCH 08/17] Fix FunctionNode serialization --- src/expression/node/FunctionNode.js | 11 ++++++----- test/unit-tests/expression/node/FunctionNode.test.js | 4 ++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/expression/node/FunctionNode.js b/src/expression/node/FunctionNode.js index c873bff1c7..98f21cd576 100644 --- a/src/expression/node/FunctionNode.js +++ b/src/expression/node/FunctionNode.js @@ -10,10 +10,11 @@ const name = 'FunctionNode' const dependencies = [ 'math', 'Node', - 'SymbolNode' + 'SymbolNode', + 'fromJSON' ] -export const createFunctionNode = /* #__PURE__ */ factory(name, dependencies, ({ math, Node, SymbolNode }) => { +export const createFunctionNode = /* #__PURE__ */ factory(name, dependencies, ({ math, Node, SymbolNode, fromJSON }) => { /* format to fixed length */ const strin = entity => format(entity, { truncate: 78 }) @@ -376,8 +377,8 @@ export const createFunctionNode = /* #__PURE__ */ factory(name, dependencies, ({ toJSON () { return { mathjs: name, - fn: this.fn, - args: this.args + fn: this.fn?.toJSON(), + args: this.args?.map(a => a.toJSON()) } } @@ -389,7 +390,7 @@ export const createFunctionNode = /* #__PURE__ */ factory(name, dependencies, ({ * @returns {FunctionNode} */ static fromJSON = function (json) { - return new FunctionNode(json.fn, json.args) + return new FunctionNode(fromJSON(json.fn), json.args?.map(fromJSON)) } /** diff --git a/test/unit-tests/expression/node/FunctionNode.test.js b/test/unit-tests/expression/node/FunctionNode.test.js index b294e1c4fc..6c084c5fc7 100644 --- a/test/unit-tests/expression/node/FunctionNode.test.js +++ b/test/unit-tests/expression/node/FunctionNode.test.js @@ -466,8 +466,8 @@ describe('FunctionNode', function () { assert.deepStrictEqual(json, { mathjs: 'FunctionNode', - fn: a, - args: [b, c] + fn: a.toJSON(), + args: [b.toJSON(), c.toJSON()] }) const parsed = FunctionNode.fromJSON(json) From 722c9e9f21a506032ce0b1b983a1fc57d638640a Mon Sep 17 00:00:00 2001 From: Matt Vague Date: Mon, 12 Sep 2022 13:24:49 -0700 Subject: [PATCH 09/17] Fix IndexNode serialization --- src/expression/node/IndexNode.js | 9 +++++---- test/unit-tests/expression/node/IndexNode.test.js | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/expression/node/IndexNode.js b/src/expression/node/IndexNode.js index bf4186bbc1..9b04a56fca 100644 --- a/src/expression/node/IndexNode.js +++ b/src/expression/node/IndexNode.js @@ -7,10 +7,11 @@ import { escape } from '../../utils/string.js' const name = 'IndexNode' const dependencies = [ 'Node', - 'size' + 'size', + 'fromJSON' ] -export const createIndexNode = /* #__PURE__ */ factory(name, dependencies, ({ Node, size }) => { +export const createIndexNode = /* #__PURE__ */ factory(name, dependencies, ({ Node, size, fromJSON }) => { class IndexNode extends Node { /** * @constructor IndexNode @@ -182,7 +183,7 @@ export const createIndexNode = /* #__PURE__ */ factory(name, dependencies, ({ No toJSON () { return { mathjs: name, - dimensions: this.dimensions, + dimensions: this.dimensions?.map(d => d.toJSON()), dotNotation: this.dotNotation } } @@ -196,7 +197,7 @@ export const createIndexNode = /* #__PURE__ */ factory(name, dependencies, ({ No * @returns {IndexNode} */ static fromJSON (json) { - return new IndexNode(json.dimensions, json.dotNotation) + return new IndexNode(json.dimensions?.map(fromJSON), json.dotNotation) } /** diff --git a/test/unit-tests/expression/node/IndexNode.test.js b/test/unit-tests/expression/node/IndexNode.test.js index 6ecb9b6916..9968453989 100644 --- a/test/unit-tests/expression/node/IndexNode.test.js +++ b/test/unit-tests/expression/node/IndexNode.test.js @@ -232,7 +232,7 @@ describe('IndexNode', function () { assert.deepStrictEqual(json, { mathjs: 'IndexNode', - dimensions: [prop], + dimensions: [prop.toJSON()], dotNotation: true }) From 495ad93d614d8f068429efedc6a8e12063fdd1ef Mon Sep 17 00:00:00 2001 From: Matt Vague Date: Mon, 12 Sep 2022 13:25:04 -0700 Subject: [PATCH 10/17] Fix ObjectNode serialization SQUISH object node --- src/expression/node/ObjectNode.js | 9 +++++---- test/unit-tests/expression/node/ObjectNode.test.js | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/expression/node/ObjectNode.js b/src/expression/node/ObjectNode.js index 99c8b5b0dc..4c209d7c10 100644 --- a/src/expression/node/ObjectNode.js +++ b/src/expression/node/ObjectNode.js @@ -6,10 +6,11 @@ import { factory } from '../../utils/factory.js' const name = 'ObjectNode' const dependencies = [ - 'Node' + 'Node', + 'fromJSON' ] -export const createObjectNode = /* #__PURE__ */ factory(name, dependencies, ({ Node }) => { +export const createObjectNode = /* #__PURE__ */ factory(name, dependencies, ({ Node, fromJSON }) => { class ObjectNode extends Node { /** * @constructor ObjectNode @@ -148,7 +149,7 @@ export const createObjectNode = /* #__PURE__ */ factory(name, dependencies, ({ N toJSON () { return { mathjs: name, - properties: this.properties + properties: Object.keys(this.properties).reduce((object, prop) => ({ ...object, [prop]: this.properties[prop]?.toJSON() }), {}) } } @@ -160,7 +161,7 @@ export const createObjectNode = /* #__PURE__ */ factory(name, dependencies, ({ N * @returns {ObjectNode} */ static fromJSON (json) { - return new ObjectNode(json.properties) + return new ObjectNode(Object.keys(json.properties).reduce((object, prop) => ({ ...object, [prop]: fromJSON(json.properties[prop]) }), {})) } /** diff --git a/test/unit-tests/expression/node/ObjectNode.test.js b/test/unit-tests/expression/node/ObjectNode.test.js index f94f41e06d..8b01638148 100644 --- a/test/unit-tests/expression/node/ObjectNode.test.js +++ b/test/unit-tests/expression/node/ObjectNode.test.js @@ -266,7 +266,7 @@ describe('ObjectNode', function () { assert.deepStrictEqual(json, { mathjs: 'ObjectNode', - properties: { b, c } + properties: { b: b.toJSON(), c: c.toJSON() } }) const parsed = ObjectNode.fromJSON(json) From e683c7cf118b609272b1a903fe468e8b744eadc4 Mon Sep 17 00:00:00 2001 From: Matt Vague Date: Mon, 12 Sep 2022 13:29:10 -0700 Subject: [PATCH 11/17] Fix ParenthesisNode serialization --- src/expression/node/ParenthesisNode.js | 9 +++++---- test/unit-tests/expression/node/ParenthesisNode.test.js | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/expression/node/ParenthesisNode.js b/src/expression/node/ParenthesisNode.js index 3a6848d3d4..17e18a3bdc 100644 --- a/src/expression/node/ParenthesisNode.js +++ b/src/expression/node/ParenthesisNode.js @@ -3,10 +3,11 @@ import { factory } from '../../utils/factory.js' const name = 'ParenthesisNode' const dependencies = [ - 'Node' + 'Node', + 'fromJSON' ] -export const createParenthesisNode = /* #__PURE__ */ factory(name, dependencies, ({ Node }) => { +export const createParenthesisNode = /* #__PURE__ */ factory(name, dependencies, ({ Node, fromJSON }) => { class ParenthesisNode extends Node { /** * @constructor ParenthesisNode @@ -102,7 +103,7 @@ export const createParenthesisNode = /* #__PURE__ */ factory(name, dependencies, * @returns {Object} */ toJSON () { - return { mathjs: name, content: this.content } + return { mathjs: name, content: this.content?.toJSON() } } /** @@ -113,7 +114,7 @@ export const createParenthesisNode = /* #__PURE__ */ factory(name, dependencies, * @returns {ParenthesisNode} */ static fromJSON (json) { - return new ParenthesisNode(json.content) + return new ParenthesisNode(fromJSON(json.content)) } /** diff --git a/test/unit-tests/expression/node/ParenthesisNode.test.js b/test/unit-tests/expression/node/ParenthesisNode.test.js index b5ae673582..5c489ea545 100644 --- a/test/unit-tests/expression/node/ParenthesisNode.test.js +++ b/test/unit-tests/expression/node/ParenthesisNode.test.js @@ -173,7 +173,7 @@ describe('ParenthesisNode', function () { assert.deepStrictEqual(json, { mathjs: 'ParenthesisNode', - content: b + content: b.toJSON() }) const parsed = ParenthesisNode.fromJSON(json) From 002bd5a63d84a3e50406b90b742c2ccc72fe8bea Mon Sep 17 00:00:00 2001 From: Matt Vague Date: Mon, 12 Sep 2022 13:32:23 -0700 Subject: [PATCH 12/17] Fix RangeNodeSerialization --- src/expression/node/RangeNode.js | 13 +++++++------ test/unit-tests/expression/node/RangeNode.test.js | 6 +++--- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/expression/node/RangeNode.js b/src/expression/node/RangeNode.js index dd814d058a..e673a4f20d 100644 --- a/src/expression/node/RangeNode.js +++ b/src/expression/node/RangeNode.js @@ -4,10 +4,11 @@ import { getPrecedence } from '../operators.js' const name = 'RangeNode' const dependencies = [ - 'Node' + 'Node', + 'fromJSON' ] -export const createRangeNode = /* #__PURE__ */ factory(name, dependencies, ({ Node }) => { +export const createRangeNode = /* #__PURE__ */ factory(name, dependencies, ({ Node, fromJSON }) => { /** * Calculate the necessary parentheses * @param {Node} node @@ -194,9 +195,9 @@ export const createRangeNode = /* #__PURE__ */ factory(name, dependencies, ({ No toJSON () { return { mathjs: name, - start: this.start, - end: this.end, - step: this.step + start: this.start?.toJSON(), + end: this.end?.toJSON(), + step: this.step?.toJSON() } } @@ -209,7 +210,7 @@ export const createRangeNode = /* #__PURE__ */ factory(name, dependencies, ({ No * @returns {RangeNode} */ static fromJSON (json) { - return new RangeNode(json.start, json.end, json.step) + return new RangeNode(fromJSON(json.start), fromJSON(json.end), fromJSON(json.step)) } /** diff --git a/test/unit-tests/expression/node/RangeNode.test.js b/test/unit-tests/expression/node/RangeNode.test.js index 00e07f20ec..d7ade39a01 100644 --- a/test/unit-tests/expression/node/RangeNode.test.js +++ b/test/unit-tests/expression/node/RangeNode.test.js @@ -327,9 +327,9 @@ describe('RangeNode', function () { assert.deepStrictEqual(json, { mathjs: 'RangeNode', - start: a, - end: b, - step: c + start: a.toJSON(), + end: b.toJSON(), + step: c.toJSON() }) const parsed = RangeNode.fromJSON(json) From f53c87c67c81b9ce1b01d258fc9be99f49478adf Mon Sep 17 00:00:00 2001 From: Matt Vague Date: Mon, 12 Sep 2022 13:34:22 -0700 Subject: [PATCH 13/17] Fix RelationalNode serialization --- src/expression/node/RelationalNode.js | 9 +++++---- test/unit-tests/expression/node/RelationalNode.test.js | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/expression/node/RelationalNode.js b/src/expression/node/RelationalNode.js index 66dffea1c0..aaccabb08c 100644 --- a/src/expression/node/RelationalNode.js +++ b/src/expression/node/RelationalNode.js @@ -6,10 +6,11 @@ import { factory } from '../../utils/factory.js' const name = 'RelationalNode' const dependencies = [ - 'Node' + 'Node', + 'fromJSON' ] -export const createRelationalNode = /* #__PURE__ */ factory(name, dependencies, ({ Node }) => { +export const createRelationalNode = /* #__PURE__ */ factory(name, dependencies, ({ Node, fromJSON }) => { const operatorMap = { equal: '==', unequal: '!=', @@ -149,7 +150,7 @@ export const createRelationalNode = /* #__PURE__ */ factory(name, dependencies, return { mathjs: name, conditionals: this.conditionals, - params: this.params + params: this.params?.map(c => c.toJSON()) } } @@ -162,7 +163,7 @@ export const createRelationalNode = /* #__PURE__ */ factory(name, dependencies, * @returns {RelationalNode} */ static fromJSON (json) { - return new RelationalNode(json.conditionals, json.params) + return new RelationalNode(json.conditionals, json.params.map(fromJSON)) } /** diff --git a/test/unit-tests/expression/node/RelationalNode.test.js b/test/unit-tests/expression/node/RelationalNode.test.js index 3f67550d60..f4f76eb99b 100644 --- a/test/unit-tests/expression/node/RelationalNode.test.js +++ b/test/unit-tests/expression/node/RelationalNode.test.js @@ -230,7 +230,7 @@ describe('RelationalNode', function () { assert.deepStrictEqual(json, { mathjs: 'RelationalNode', conditionals: ['smaller', 'smaller'], - params: [one, x, three] + params: [one.toJSON(), x.toJSON(), three.toJSON()] }) const parsed = RelationalNode.fromJSON(json) From ec41e445289896dbe5f345da67f4545839cd3aa4 Mon Sep 17 00:00:00 2001 From: Matt Vague Date: Mon, 12 Sep 2022 13:41:56 -0700 Subject: [PATCH 14/17] Fix `fromJSON` --- test/unit-tests/json/fromJSON.test.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/unit-tests/json/fromJSON.test.js b/test/unit-tests/json/fromJSON.test.js index d7da1fbd8e..cbe9c4cebb 100644 --- a/test/unit-tests/json/fromJSON.test.js +++ b/test/unit-tests/json/fromJSON.test.js @@ -2,7 +2,7 @@ import assert from 'assert' import math from '../../../src/defaultInstance.js' const fromJSON = math.fromJSON -describe('toObject', function () { +describe('fromJSON', function () { it('output JSON for a node', function () { const node = new math.OperatorNode('+', 'add', [ new math.SymbolNode('x'), @@ -13,9 +13,11 @@ describe('toObject', function () { fromJSON({ args: [ { + mathjs: 'SymbolNode', name: 'x' }, { + mathjs: 'ConstantNode', value: 2 } ], From b848c75fb37e0cc6f0b6e3b35aebca96c4e90c30 Mon Sep 17 00:00:00 2001 From: Matt Vague Date: Mon, 12 Sep 2022 13:48:22 -0700 Subject: [PATCH 15/17] Fix AccessorNode serialization --- src/expression/node/AccessorNode.js | 9 +++++---- test/unit-tests/expression/node/AccessorNode.test.js | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/expression/node/AccessorNode.js b/src/expression/node/AccessorNode.js index b3c289986c..cfd66a76b0 100644 --- a/src/expression/node/AccessorNode.js +++ b/src/expression/node/AccessorNode.js @@ -16,10 +16,11 @@ import { accessFactory } from './utils/access.js' const name = 'AccessorNode' const dependencies = [ 'subset', - 'Node' + 'Node', + 'fromJSON' ] -export const createAccessorNode = /* #__PURE__ */ factory(name, dependencies, ({ subset, Node }) => { +export const createAccessorNode = /* #__PURE__ */ factory(name, dependencies, ({ subset, Node, fromJSON }) => { const access = accessFactory({ subset }) /** @@ -191,7 +192,7 @@ export const createAccessorNode = /* #__PURE__ */ factory(name, dependencies, ({ toJSON () { return { mathjs: name, - object: this.object, + object: this.object?.toJSON(), index: this.index } } @@ -205,7 +206,7 @@ export const createAccessorNode = /* #__PURE__ */ factory(name, dependencies, ({ * @returns {AccessorNode} */ static fromJSON (json) { - return new AccessorNode(json.object, json.index) + return new AccessorNode(fromJSON(json.object), json.index) } } diff --git a/test/unit-tests/expression/node/AccessorNode.test.js b/test/unit-tests/expression/node/AccessorNode.test.js index 10b21cf9f8..e1cd5eaba9 100644 --- a/test/unit-tests/expression/node/AccessorNode.test.js +++ b/test/unit-tests/expression/node/AccessorNode.test.js @@ -527,7 +527,7 @@ describe('AccessorNode', function () { assert.deepStrictEqual(json, { mathjs: 'AccessorNode', index: node.index, - object: a + object: a.toJSON() }) const parsed = AccessorNode.fromJSON(json) From 302e8c77d081fe8c9398d132cb8b87358d16f902 Mon Sep 17 00:00:00 2001 From: Matt Vague Date: Mon, 12 Sep 2022 13:52:47 -0700 Subject: [PATCH 16/17] Try and fix fromJSON --- src/expression/node/AccessorNode.js | 4 ++-- src/factoriesAny.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/expression/node/AccessorNode.js b/src/expression/node/AccessorNode.js index cfd66a76b0..2d028c51fb 100644 --- a/src/expression/node/AccessorNode.js +++ b/src/expression/node/AccessorNode.js @@ -16,8 +16,8 @@ import { accessFactory } from './utils/access.js' const name = 'AccessorNode' const dependencies = [ 'subset', - 'Node', - 'fromJSON' + 'fromJSON', + 'Node' ] export const createAccessorNode = /* #__PURE__ */ factory(name, dependencies, ({ subset, Node, fromJSON }) => { diff --git a/src/factoriesAny.js b/src/factoriesAny.js index 747c9b2ccc..208454a322 100644 --- a/src/factoriesAny.js +++ b/src/factoriesAny.js @@ -1,3 +1,4 @@ +export { createfromJSON } from './json/fromJSON.js' export { createTyped } from './core/function/typed.js' export { createResultSet } from './type/resultset/ResultSet.js' export { createBigNumberClass } from './type/bignumber/BigNumber.js' @@ -256,7 +257,6 @@ export { createDerivative } from './function/algebra/derivative.js' export { createRationalize } from './function/algebra/rationalize.js' export { createReviver } from './json/reviver.js' export { createReplacer } from './json/replacer.js' -export { createfromJSON } from './json/fromJSON.js' export { createE, createUppercaseE, From f270a5715eb154c97f137f8997747a2e2585a1d7 Mon Sep 17 00:00:00 2001 From: Matt Vague Date: Mon, 12 Sep 2022 17:07:57 -0700 Subject: [PATCH 17/17] Add missing factory --- src/factoriesNumber.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/factoriesNumber.js b/src/factoriesNumber.js index 9ae85a9b40..bb681a4e0d 100644 --- a/src/factoriesNumber.js +++ b/src/factoriesNumber.js @@ -324,6 +324,7 @@ export { createIsPrime } from './function/utils/isPrime.js' export { createNumeric } from './function/utils/numeric.js' // json +export { createfromJSON } from './json/fromJSON.js' export { createReviver } from './json/reviver.js' export { createReplacer } from './json/replacer.js'