From a1f60db0d15208a3be4e40813b59e9cd41c8d544 Mon Sep 17 00:00:00 2001 From: jos Date: Fri, 14 Nov 2014 11:25:48 +0100 Subject: [PATCH] Created Node.map, changed traverse to use Node.map --- lib/expression/node/ArrayNode.js | 28 ++++-- lib/expression/node/AssignmentNode.js | 22 +++-- lib/expression/node/BlockNode.js | 33 +++++-- lib/expression/node/ConditionalNode.js | 30 +++--- lib/expression/node/ConstantNode.js | 18 +++- lib/expression/node/FunctionAssignmentNode.js | 23 +++-- lib/expression/node/FunctionNode.js | 36 ++++--- lib/expression/node/IndexNode.js | 35 ++++--- lib/expression/node/Node.js | 95 ++++++++++++------- lib/expression/node/OperatorNode.js | 35 ++++--- lib/expression/node/RangeNode.js | 32 ++++--- lib/expression/node/SymbolNode.js | 17 +++- lib/expression/node/UpdateNode.js | 27 ++++-- test/expression/node/ArrayNode.test.js | 15 +-- test/expression/node/AssignmentNode.test.js | 4 +- test/expression/node/BlockNode.test.js | 5 +- test/expression/node/ConditionalNode.test.js | 12 +-- test/expression/node/ConstantNode.test.js | 2 +- .../node/FunctionAssignmentNode.test.js | 4 +- test/expression/node/FunctionNode.test.js | 17 ++-- test/expression/node/IndexNode.test.js | 11 ++- test/expression/node/Node.test.js | 17 ++-- test/expression/node/OperatorNode.test.js | 8 +- test/expression/node/RangeNode.test.js | 18 ++-- test/expression/node/UpdateNode.test.js | 8 +- 25 files changed, 351 insertions(+), 201 deletions(-) diff --git a/lib/expression/node/ArrayNode.js b/lib/expression/node/ArrayNode.js index 9ec6206af..86835947a 100644 --- a/lib/expression/node/ArrayNode.js +++ b/lib/expression/node/ArrayNode.js @@ -52,26 +52,36 @@ ArrayNode.prototype._compile = function (defs) { }; /** - * Recursively execute a callback for each of the child nodes of this node - * @param {function(Node, string, Node)} callback - * @private + * Execute a callback for each of the child nodes of this node + * @param {function(child: Node, path: string, parent: Node)} callback */ -ArrayNode.prototype._traverse = function (callback) { +ArrayNode.prototype.forEach = function (callback) { for (var i = 0; i < this.nodes.length; i++) { var node = this.nodes[i]; callback(node, 'nodes.' + i, this); - node._traverse(callback); } }; /** - * Create a clone of this node + * Create a new ArrayNode having it's childs be the results of calling + * the provided callback function for each of the childs of the original node. + * @param {function(child: Node, path: string, parent: Node): Node} callback + * @returns {ArrayNode} Returns a transformed copy of the node + */ +ArrayNode.prototype.map = function (callback) { + var nodes = []; + for (var i = 0; i < this.nodes.length; i++) { + nodes[i] = callback(this.nodes[i], 'nodes.' + i, this); + } + return new ArrayNode(nodes); +}; + +/** + * Create a clone of this node, a shallow copy * @return {ArrayNode} */ ArrayNode.prototype.clone = function() { - return new ArrayNode(this.nodes.map(function(node) { - return node.clone(); - })) + return new ArrayNode(this.nodes.slice(0)) }; /** diff --git a/lib/expression/node/AssignmentNode.js b/lib/expression/node/AssignmentNode.js index b9d5890dd..bc52ec814 100644 --- a/lib/expression/node/AssignmentNode.js +++ b/lib/expression/node/AssignmentNode.js @@ -47,21 +47,29 @@ AssignmentNode.prototype._compile = function (defs) { /** - * Recursively execute a callback for each of the child nodes of this node - * @param {function(Node, string, Node)} callback - * @private + * Execute a callback for each of the child nodes of this node + * @param {function(child: Node, path: string, parent: Node)} callback */ -AssignmentNode.prototype._traverse = function (callback) { +AssignmentNode.prototype.forEach = function (callback) { callback(this.expr, 'expr', this); - this.expr._traverse(callback); }; /** - * Create a clone of this node + * Create a new AssignmentNode having it's childs be the results of calling + * the provided callback function for each of the childs of the original node. + * @param {function(child: Node, path: string, parent: Node): Node} callback + * @returns {AssignmentNode} Returns a transformed copy of the node + */ +AssignmentNode.prototype.map = function (callback) { + return new AssignmentNode(this.name, callback(this.expr, 'expr', this)); +}; + +/** + * Create a clone of this node, a shallow copy * @return {AssignmentNode} */ AssignmentNode.prototype.clone = function() { - return new AssignmentNode(this.name, this.expr.clone()); + return new AssignmentNode(this.name, this.expr); }; /** diff --git a/lib/expression/node/BlockNode.js b/lib/expression/node/BlockNode.js index 7819f97be..3075c4486 100644 --- a/lib/expression/node/BlockNode.js +++ b/lib/expression/node/BlockNode.js @@ -66,26 +66,41 @@ BlockNode.prototype._compile = function (defs) { }; /** - * Recursively execute a callback for each of the child blocks of this node - * @param {function(Node, string, Node)} callback - * @private + * Execute a callback for each of the child blocks of this node + * @param {function(child: Node, path: string, parent: Node)} callback */ -BlockNode.prototype._traverse = function (callback) { +BlockNode.prototype.forEach = function (callback) { for (var i = 0; i < this.blocks.length; i++) { - var node = this.blocks[i].node; - callback(node, 'blocks.' + i + '.node', this); - node._traverse(callback); + callback(this.blocks[i].node, 'blocks.' + i + '.node', this); } }; /** - * Create a clone of this node + * Create a new BlockNode having it's childs be the results of calling + * the provided callback function for each of the childs of the original node. + * @param {function(child: Node, path: string, parent: Node): Node} callback + * @returns {BlockNode} Returns a transformed copy of the node + */ +BlockNode.prototype.map = function (callback) { + var blocks = []; + for (var i = 0; i < this.blocks.length; i++) { + var block = this.blocks[i]; + blocks[i] = { + node: callback(block.node, 'blocks.' + i + '.node', this), + visible: block.visible + }; + } + return new BlockNode(blocks); +}; + +/** + * Create a clone of this node, a shallow copy * @return {BlockNode} */ BlockNode.prototype.clone = function() { var blocks = this.blocks.map(function(block) { return { - node: block.node.clone(), + node: block.node, visible: block.visible }; }); diff --git a/lib/expression/node/ConditionalNode.js b/lib/expression/node/ConditionalNode.js index 30e9074b5..a159fe81e 100644 --- a/lib/expression/node/ConditionalNode.js +++ b/lib/expression/node/ConditionalNode.js @@ -83,27 +83,35 @@ ConditionalNode.prototype._compile = function(defs) { }; /** - * Recursively execute a callback for each of the child nodes of this node - * @param {function(Node, string, Node)} callback - * @private + * Execute a callback for each of the child nodes of this node + * @param {function(child: Node, path: string, parent: Node)} callback */ -ConditionalNode.prototype._traverse = function (callback) { +ConditionalNode.prototype.forEach = function (callback) { callback(this.condition, 'condition', this); - this.condition._traverse(callback); - callback(this.trueExpr, 'trueExpr', this); - this.trueExpr._traverse(callback); - callback(this.falseExpr, 'falseExpr', this); - this.falseExpr._traverse(callback); }; /** - * Create a clone of this node + * Create a new ConditionalNode having it's childs be the results of calling + * the provided callback function for each of the childs of the original node. + * @param {function(child: Node, path: string, parent: Node): Node} callback + * @returns {ConditionalNode} Returns a transformed copy of the node + */ +ConditionalNode.prototype.map = function (callback) { + return new ConditionalNode( + callback(this.condition, 'condition', this), + callback(this.trueExpr, 'trueExpr', this), + callback(this.falseExpr, 'falseExpr', this) + ); +}; + +/** + * Create a clone of this node, a shallow copy * @return {ConditionalNode} */ ConditionalNode.prototype.clone = function() { - return new ConditionalNode(this.condition.clone(), this.trueExpr.clone(), this.falseExpr.clone()); + return new ConditionalNode(this.condition, this.trueExpr, this.falseExpr); }; /** diff --git a/lib/expression/node/ConstantNode.js b/lib/expression/node/ConstantNode.js index f8a5d3507..796587fc6 100644 --- a/lib/expression/node/ConstantNode.js +++ b/lib/expression/node/ConstantNode.js @@ -115,15 +115,25 @@ ConstantNode.prototype._compile = function (defs) { /** * Execute a callback for each of the child nodes of this node - * @param {function(Node, string, Node)} callback - * @private + * @param {function(child: Node, path: string, parent: Node)} callback */ -ConstantNode.prototype._traverse = function (callback) { +ConstantNode.prototype.forEach = function (callback) { // nothing to do, we don't have childs }; + /** - * Create a clone of this node + * 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. + * @param {function(child: Node, path: string, parent: Node) : Node} callback + * @returns {ConstantNode} Returns a clone of the node + */ +ConstantNode.prototype.map = function (callback) { + return this.clone(); +}; + +/** + * Create a clone of this node, a shallow copy * @return {ConstantNode} */ ConstantNode.prototype.clone = function() { diff --git a/lib/expression/node/FunctionAssignmentNode.js b/lib/expression/node/FunctionAssignmentNode.js index 2a1ee2a99..141b08902 100644 --- a/lib/expression/node/FunctionAssignmentNode.js +++ b/lib/expression/node/FunctionAssignmentNode.js @@ -64,21 +64,32 @@ FunctionAssignmentNode.prototype._compile = function (defs) { }; /** - * Recursively execute a callback for each of the child nodes of this node - * @param {function(Node, string, Node)} callback + * Execute a callback for each of the child nodes of this node + * @param {function(child: Node, path: string, parent: Node)} callback * @private */ -FunctionAssignmentNode.prototype._traverse = function (callback) { +FunctionAssignmentNode.prototype.forEach = function (callback) { callback(this.expr, 'expr', this); - this.expr._traverse(callback); }; /** - * Create a clone of this node + * Create a new FunctionAssignmentNode having it's childs be the results of calling + * the provided callback function for each of the childs of the original node. + * @param {function(child: Node, path: string, parent: Node): Node} callback + * @returns {FunctionAssignmentNode} Returns a transformed copy of the node + */ +FunctionAssignmentNode.prototype.map = function (callback) { + var expr = callback(this.expr, 'expr', this); + + return new FunctionAssignmentNode(this.name, this.params.slice(0), expr); +}; + +/** + * Create a clone of this node, a shallow copy * @return {FunctionAssignmentNode} */ FunctionAssignmentNode.prototype.clone = function() { - return new FunctionAssignmentNode(this.name, this.params.concat(), this.expr.clone()); + return new FunctionAssignmentNode(this.name, this.params.slice(0), this.expr); }; /** diff --git a/lib/expression/node/FunctionNode.js b/lib/expression/node/FunctionNode.js index 14aeb0844..972ab11bf 100644 --- a/lib/expression/node/FunctionNode.js +++ b/lib/expression/node/FunctionNode.js @@ -46,8 +46,8 @@ FunctionNode.prototype._compile = function (defs) { var isRaw = (typeof fn === 'function') && (fn.rawArgs == true); // compile the parameters - var args = this.args.map(function (param) { - return param._compile(defs); + var args = this.args.map(function (arg) { + return arg._compile(defs); }); if (isRaw) { @@ -71,27 +71,35 @@ FunctionNode.prototype._compile = function (defs) { }; /** - * Recursively execute a callback for each of the child nodes of this node - * @param {function(Node, string, Node)} callback - * @private + * Execute a callback for each of the child nodes of this node + * @param {function(child: Node, path: string, parent: Node)} callback */ -FunctionNode.prototype._traverse = function (callback) { - // args +FunctionNode.prototype.forEach = function (callback) { for (var i = 0; i < this.args.length; i++) { - var param = this.args[i]; - callback(param, 'args.' + i, this); - param._traverse(callback); + callback(this.args[i], 'args.' + i, this); } }; /** - * Create a clone of this node + * Create a new FunctionNode having it's childs be the results of calling + * the provided callback function for each of the childs of the original node. + * @param {function(child: Node, path: string, parent: Node): Node} callback + * @returns {FunctionNode} Returns a transformed copy of the node + */ +FunctionNode.prototype.map = function (callback) { + var args = []; + for (var i = 0; i < this.args.length; i++) { + args[i] = callback(this.args[i], 'args.' + i, this); + } + return new FunctionNode(this.name, args); +}; + +/** + * Create a clone of this node, a shallow copy * @return {FunctionNode} */ FunctionNode.prototype.clone = function() { - return new FunctionNode(this.name, this.args.map(function (param) { - return param.clone(); - })); + return new FunctionNode(this.name, this.args.slice(0)); }; /** diff --git a/lib/expression/node/IndexNode.js b/lib/expression/node/IndexNode.js index b8270e957..7c9cc1b51 100644 --- a/lib/expression/node/IndexNode.js +++ b/lib/expression/node/IndexNode.js @@ -152,23 +152,36 @@ IndexNode.prototype.compileSubset = function(defs, replacement) { }; /** - * Recursively execute a callback for each of the child nodes of this node - * @param {function(Node, string, Node)} callback - * @private + * Execute a callback for each of the child nodes of this node + * @param {function(child: Node, path: string, parent: Node)} callback */ -IndexNode.prototype._traverse = function (callback) { +IndexNode.prototype.forEach = function (callback) { // object callback(this.object, 'object', this); - this.object._traverse(callback); // ranges for (var i = 0; i < this.ranges.length; i++) { - var range = this.ranges[i]; - callback(range, 'ranges.' + i, this); - range._traverse(callback); + callback(this.ranges[i], 'ranges.' + i, this); } }; +/** + * Create a new IndexNode having it's childs be the results of calling + * the provided callback function for each of the childs of the original node. + * @param {function(child: Node, path: string, parent: Node): Node} callback + * @returns {IndexNode} Returns a transformed copy of the node + */ +IndexNode.prototype.map = function (callback) { + var object = callback(this.object, 'object', this); + + var ranges = []; + for (var i = 0; i < this.ranges.length; i++) { + ranges[i] = callback(this.ranges[i], 'ranges.' + i, this); + } + + return new IndexNode(object, ranges); +}; + /** * Get the name of the object linked to this IndexNode * @return {string} name @@ -178,13 +191,11 @@ IndexNode.prototype.objectName = function() { }; /** - * Create a clone of this node + * Create a clone of this node, a shallow copy * @return {IndexNode} */ IndexNode.prototype.clone = function() { - return new IndexNode(this.object.clone(), this.ranges.map(function (range) { - return range.clone(); - })); + return new IndexNode(this.object, this.ranges.slice(0)); }; /** diff --git a/lib/expression/node/Node.js b/lib/expression/node/Node.js index 4f438d5db..3b7b17da1 100644 --- a/lib/expression/node/Node.js +++ b/lib/expression/node/Node.js @@ -76,38 +76,59 @@ Node.prototype._compile = function (defs) { /** * Execute a callback for each of the child nodes of this node - * @param {function(Node, string, Node)} callback + * @param {function(child: Node, path: string, parent: Node)} callback * @private */ -Node.prototype._traverse = function (callback) { - // must be implemented by each of the Node implementations having child nodes - throw new Error('Cannot traverse a Node interface'); +Node.prototype.forEach = function (callback) { + // must be implemented by each of the Node implementations + throw new Error('Cannot run forEach on a Node interface'); +}; + +/** + * Create a new Node having it's childs be the results of calling + * the provided callback function for each of the childs of the original node. + * @param {function(child: Node, path: string, parent: Node): Node} callback + * @returns {OperatorNode} Returns a transformed copy of the node + */ +Node.prototype.map = function (callback) { + // must be implemented by each of the Node implementations + throw new Error('Cannot run map on a Node interface'); }; /** * Recursively traverse all nodes in a node tree. Executes given callback for - * this node and each of its child nodes. Similar to Array.forEach, except - * recursive. - * @param {function(Node, string, Node)} callback + * this node and each of its child nodes. + * @param {function(node: Node, path: string, parent: Node)} callback * A callback called for every node in the node tree. - * Signature: callback(node: Node, index: string, parent: Node) : Node */ Node.prototype.traverse = function (callback) { // execute callback for itself callback(this, null, null); - // traverse over all children + // recursively traverse over all childs this._traverse(callback); }; /** - * Transform a node tree via a transform function. Similar to Array.map, - * but recursively executed on all nodes in the node tree. + * Recursively traverse all childs of this node + * @param {function(node: Node, path: string, parent: Node)} callback + * A callback called for every node in the node tree. + * @private + */ +Node.prototype._traverse = function (callback) { + this.forEach(function(child, path, parent) { + callback(child, path, parent); + child._traverse(callback); + }); +}; + +/** + * Recursively transform a node tree via a transform function. * * For example, to replace all nodes of type SymbolNode having name 'x' with a * ConstantNode with value 2: * - * var res = Node.transform(function (node) { + * var res = Node.transform(function (node, path, parent) { * if (node instanceof SymbolNode) && (node.name == 'x')) { * return new ConstantNode(2); * } @@ -116,7 +137,7 @@ Node.prototype.traverse = function (callback) { * } * }); * - * @param {function(Node, string, Node)} callback + * @param {function(node: Node, path: string, parent: Node) : Node} callback * A mapping function accepting a node, and returning * a replacement for the node or the original node. * Signature: callback(node: Node, index: string, parent: Node) : Node @@ -124,30 +145,33 @@ Node.prototype.traverse = function (callback) { */ Node.prototype.transform = function (callback) { // check itself - var res = callback(this, null, null); - - if (res === this) { - // recurse over the child nodes - res._traverse(function (node, index, parent) { - var replacement = callback(node, index, parent); - if (index.indexOf('.') !== -1) { - // traverse path - var props = index.split('.'); - var obj = parent; - while (props.length > 1) { - obj = obj[props.shift()]; - } - obj[props.shift()] = replacement; - } - else { - parent[index] = replacement; - } - }); + var replacement = callback(this, null, null); + if (replacement !== this) { + return replacement; } - return res; + // traverse over all childs + return this._transform(callback); }; +/** + * Recursively transform all childs of this node + * @param {function(node: Node, path: string, parent: Node) : Node} callback + * A mapping function accepting a node, and returning + * a replacement for the node or the original node. + * Signature: callback(node: Node, index: string, parent: Node) : Node + * @return {Node} Returns the original node or its replacement + * @private + */ +Node.prototype._transform = function (callback) { + return this.map(function(child, path, parent) { + var replacement = callback(child, path, parent); + return (replacement !== child) ? replacement : child._transform(callback); + }); +}; + +// TODO: create map and forEach + /** * Find any node in the node tree matching given filter function. For example, to * find all nodes of type SymbolNode having name 'x': @@ -184,11 +208,12 @@ Node.prototype.match = function () { }; /** - * Create a clone of this node + * Create a clone of this node, a shallow copy * @return {Node} */ Node.prototype.clone = function() { - return new Node(); + // must be implemented by each of the Node implementations + throw new Error('Cannot clone a Node interface'); }; /** diff --git a/lib/expression/node/OperatorNode.js b/lib/expression/node/OperatorNode.js index 96299f86a..ea9bc0e6e 100644 --- a/lib/expression/node/OperatorNode.js +++ b/lib/expression/node/OperatorNode.js @@ -43,33 +43,42 @@ OperatorNode.prototype._compile = function (defs) { throw new Error('Function ' + this.fn + ' missing in provided namespace "math"'); } - var args = this.args.map(function (param) { - return param._compile(defs); + var args = this.args.map(function (arg) { + return arg._compile(defs); }); return 'math.' + this.fn + '(' + args.join(', ') + ')'; }; /** - * Recursively execute a callback for each of the child nodes of this node - * @param {function(Node, string, Node)} callback - * @private + * Execute a callback for each of the child nodes of this node + * @param {function(child: Node, path: string, parent: Node)} callback */ -OperatorNode.prototype._traverse = function (callback) { +OperatorNode.prototype.forEach = function (callback) { for (var i = 0; i < this.args.length; i++) { - var param = this.args[i]; - callback(param, 'args.' + i, this); - param._traverse(callback); + callback(this.args[i], 'args.' + i, this); } }; /** - * Create a clone of this node + * Create a new OperatorNode having it's childs be the results of calling + * the provided callback function for each of the childs of the original node. + * @param {function(child: Node, path: string, parent: Node): Node} callback + * @returns {OperatorNode} Returns a transformed copy of the node + */ +OperatorNode.prototype.map = function (callback) { + var args = []; + for (var i = 0; i < this.args.length; i++) { + args[i] = callback(this.args[i], 'args.' + i, this); + } + return new OperatorNode(this.op, this.fn, args); +}; + +/** + * Create a clone of this node, a shallow copy * @return {OperatorNode} */ OperatorNode.prototype.clone = function() { - return new OperatorNode(this.op, this.fn, this.args.map(function (param) { - return param.clone(); - })); + return new OperatorNode(this.op, this.fn, this.args.slice(0)); }; /** diff --git a/lib/expression/node/RangeNode.js b/lib/expression/node/RangeNode.js index a7168264e..5ffdc3607 100644 --- a/lib/expression/node/RangeNode.js +++ b/lib/expression/node/RangeNode.js @@ -49,33 +49,39 @@ RangeNode.prototype._compile = function (defs) { }; /** - * Recursively execute a callback for each of the child nodes of this node - * @param {function(Node, string, Node)} callback - * @private + * Execute a callback for each of the child nodes of this node + * @param {function(child: Node, path: string, parent: Node)} callback */ -RangeNode.prototype._traverse = function (callback) { +RangeNode.prototype.forEach = function (callback) { callback(this.start, 'start', this); - this.start._traverse(callback); if (this.step) { callback(this.step, 'step', this); - this.step._traverse(callback); } callback(this.end, 'end', this); - this.end._traverse(callback); }; /** - * Create a clone of this node + * Create a new RangeNode having it's childs be the results of calling + * the provided callback function for each of the childs of the original node. + * @param {function(child: Node, path: string, parent: Node): Node} callback + * @returns {RangeNode} Returns a transformed copy of the node + */ +RangeNode.prototype.map = function (callback) { + return new RangeNode( + callback(this.start, 'start', this), + callback(this.end, 'end', this), + this.step && callback(this.step, 'step', this) + ); +}; + +/** + * Create a clone of this node, a shallow copy * @return {RangeNode} */ RangeNode.prototype.clone = function() { - return new RangeNode( - this.start.clone(), - this.end.clone(), - this.step && this.step.clone() - ); + return new RangeNode(this.start, this.end, this.step && this.step); }; /** diff --git a/lib/expression/node/SymbolNode.js b/lib/expression/node/SymbolNode.js index 53c92de03..e6053d379 100644 --- a/lib/expression/node/SymbolNode.js +++ b/lib/expression/node/SymbolNode.js @@ -56,13 +56,22 @@ SymbolNode.prototype._compile = function (defs) { /** * Execute a callback for each of the child nodes of this node - * @param {function(Node, string, Node)} callback - * @private + * @param {function(child: Node, path: string, parent: Node)} callback */ -SymbolNode.prototype._traverse = function (callback) { +SymbolNode.prototype.forEach = function (callback) { // nothing to do, we don't have childs }; +/** + * Create a new SymbolNode having it's childs be the results of calling + * the provided callback function for each of the childs of the original node. + * @param {function(child: Node, path: string, parent: Node) : Node} callback + * @returns {SymbolNode} Returns a clone of the node + */ +SymbolNode.prototype.map = function (callback) { + return this.clone(); +}; + /** * Throws an error 'Undefined symbol {name}' * @param {String} name @@ -72,7 +81,7 @@ function undef (name) { } /** - * Create a clone of this node + * Create a clone of this node, a shallow copy * @return {SymbolNode} */ SymbolNode.prototype.clone = function() { diff --git a/lib/expression/node/UpdateNode.js b/lib/expression/node/UpdateNode.js index 65b63d1d1..56694efb5 100644 --- a/lib/expression/node/UpdateNode.js +++ b/lib/expression/node/UpdateNode.js @@ -45,24 +45,33 @@ UpdateNode.prototype._compile = function (defs) { }; /** - * Recursively execute a callback for each of the child nodes of this node - * @param {function(Node, string, Node)} callback - * @private + * Execute a callback for each of the child nodes of this node + * @param {function(child: Node, path: string, parent: Node)} callback */ -UpdateNode.prototype._traverse = function (callback) { +UpdateNode.prototype.forEach = function (callback) { callback(this.index, 'index', this); - this.index._traverse(callback); - callback(this.expr, 'expr', this); - this.expr._traverse(callback); }; /** - * Create a clone of this node + * Create a new UpdateNode having it's childs be the results of calling + * the provided callback function for each of the childs of the original node. + * @param {function(child: Node, path: string, parent: Node): Node} callback + * @returns {UpdateNode} Returns a transformed copy of the node + */ +UpdateNode.prototype.map = function (callback) { + return new UpdateNode( + callback(this.index, 'index', this), + callback(this.expr, 'expr', this) + ); +}; + +/** + * Create a clone of this node, a shallow copy * @return {UpdateNode} */ UpdateNode.prototype.clone = function() { - return new UpdateNode(this.index.clone(), this.expr.clone()); + return new UpdateNode(this.index, this.expr); }; /** diff --git a/test/expression/node/ArrayNode.test.js b/test/expression/node/ArrayNode.test.js index 80c735eed..78cac1a93 100644 --- a/test/expression/node/ArrayNode.test.js +++ b/test/expression/node/ArrayNode.test.js @@ -91,7 +91,7 @@ describe('ArrayNode', function() { return (node instanceof SymbolNode) && (node.name == 'x') ? d : node; }); - assert.strictEqual(e, c); + assert.notStrictEqual(e, c); assert.deepEqual(e.nodes[0], d); assert.deepEqual(e.nodes[1], b); }); @@ -115,25 +115,26 @@ describe('ArrayNode', function() { var c = new ArrayNode([a, b]); var count = 0; - c.traverse(function (node, index, parent) { + c.traverse(function (node, path, parent) { + console.log('traverse', node.toString(), path) count++; switch(count) { case 1: assert.strictEqual(node, c); - assert.strictEqual(index, null); + assert.strictEqual(path, null); assert.strictEqual(parent, null); break; case 2: assert.strictEqual(node, a); - assert.strictEqual(index, 'nodes.0'); + assert.strictEqual(path, 'nodes.0'); assert.strictEqual(parent, c); break; case 3: assert.strictEqual(node, b); - assert.strictEqual(index, 'nodes.1'); + assert.strictEqual(path, 'nodes.1'); assert.strictEqual(parent, c); break; } @@ -152,8 +153,8 @@ describe('ArrayNode', function() { assert(d instanceof ArrayNode); assert.deepEqual(c, d); assert.notStrictEqual(c, d); - assert.notStrictEqual(c.nodes[0], d.nodes[0]); - assert.notStrictEqual(c.nodes[1], d.nodes[1]); + assert.strictEqual(c.nodes[0], d.nodes[0]); + assert.strictEqual(c.nodes[1], d.nodes[1]); }); it ('should stringify an ArrayNode', function () { diff --git a/test/expression/node/AssignmentNode.test.js b/test/expression/node/AssignmentNode.test.js index ae750223f..466cb18dc 100644 --- a/test/expression/node/AssignmentNode.test.js +++ b/test/expression/node/AssignmentNode.test.js @@ -81,7 +81,7 @@ describe('AssignmentNode', function() { return node instanceof SymbolNode && node.name == 'x' ? e : node; }); - assert.strictEqual(f, d); + assert.notStrictEqual(f, d); assert.deepEqual(f.expr.args[0], e); assert.deepEqual(f.expr.args[1], b); }); @@ -139,7 +139,7 @@ describe('AssignmentNode', function() { assert(e instanceof AssignmentNode); assert.deepEqual(e, d); assert.notStrictEqual(e, d); - assert.notStrictEqual(e.expr, d.expr); + assert.strictEqual(e.expr, d.expr); }); it ('should stringify a AssignmentNode', function () { diff --git a/test/expression/node/BlockNode.test.js b/test/expression/node/BlockNode.test.js index ecee519a2..b81fc8cf2 100644 --- a/test/expression/node/BlockNode.test.js +++ b/test/expression/node/BlockNode.test.js @@ -91,7 +91,7 @@ describe('BlockNode', function() { return node instanceof SymbolNode && node.name == 'x' ? d : node; }); - assert.strictEqual(e, a); + assert.notStrictEqual(e, a); assert.deepEqual(e.blocks[0].node, d); assert.deepEqual(e.blocks[1].node, c); }); @@ -156,8 +156,11 @@ describe('BlockNode', function() { assert(d instanceof BlockNode); assert.deepEqual(a, d); assert.notStrictEqual(a, d); + assert.notStrictEqual(a.blocks, d.blocks); assert.notStrictEqual(a.blocks[0], d.blocks[0]); assert.notStrictEqual(a.blocks[1], d.blocks[1]); + assert.strictEqual(a.blocks[0].node, d.blocks[0].node); + assert.strictEqual(a.blocks[1].node, d.blocks[1].node); }); it ('should stringify a BlockNode', function () { diff --git a/test/expression/node/ConditionalNode.test.js b/test/expression/node/ConditionalNode.test.js index 73e369357..d9d033e90 100644 --- a/test/expression/node/ConditionalNode.test.js +++ b/test/expression/node/ConditionalNode.test.js @@ -117,7 +117,7 @@ describe('ConditionalNode', function() { return node instanceof ConstantNode && node.value == '1' ? e : node; }); - assert.strictEqual(f, n); + assert.notStrictEqual(f, n); assert.deepEqual(f.condition, e); assert.deepEqual(f.trueExpr, a); assert.deepEqual(f.falseExpr, b); @@ -134,7 +134,7 @@ describe('ConditionalNode', function() { return node instanceof ConstantNode && node.value == '2' ? e : node; }); - assert.strictEqual(f, n); + assert.notStrictEqual(f, n); assert.deepEqual(f.condition, condition); assert.deepEqual(f.trueExpr, e); assert.deepEqual(f.falseExpr, b); @@ -151,7 +151,7 @@ describe('ConditionalNode', function() { return node instanceof ConstantNode && node.value == '3' ? e : node; }); - assert.strictEqual(f, n); + assert.notStrictEqual(f, n); assert.deepEqual(f.condition, condition); assert.deepEqual(f.trueExpr, a); assert.deepEqual(f.falseExpr, e); @@ -182,9 +182,9 @@ describe('ConditionalNode', function() { assert(d instanceof ConditionalNode); assert.deepEqual(d, c); assert.notStrictEqual(d, c); - assert.notStrictEqual(d.condition, c.condition); - assert.notStrictEqual(d.trueExpr, c.trueExpr); - assert.notStrictEqual(d.falseExpr, c.falseExpr); + assert.strictEqual(d.condition, c.condition); + assert.strictEqual(d.trueExpr, c.trueExpr); + assert.strictEqual(d.falseExpr, c.falseExpr); }); it ('should stringify a ConditionalNode', function () { diff --git a/test/expression/node/ConstantNode.test.js b/test/expression/node/ConstantNode.test.js index e5ce6b9cf..5d1a99277 100644 --- a/test/expression/node/ConstantNode.test.js +++ b/test/expression/node/ConstantNode.test.js @@ -87,7 +87,7 @@ describe('ConstantNode', function() { var d = a.transform(function (node) { return node instanceof ConstantNode && node.value == '99' ? b : node; }); - assert.strictEqual(d, a); + assert.notStrictEqual(d, a); assert.deepEqual(d, a); }); diff --git a/test/expression/node/FunctionAssignmentNode.test.js b/test/expression/node/FunctionAssignmentNode.test.js index eeca3a0d3..8d38ad939 100644 --- a/test/expression/node/FunctionAssignmentNode.test.js +++ b/test/expression/node/FunctionAssignmentNode.test.js @@ -86,7 +86,7 @@ describe('FunctionAssignmentNode', function() { return node instanceof SymbolNode && node.name == 'x' ? e : node; }); - assert.strictEqual(f, n); + assert.notStrictEqual(f, n); assert.deepEqual(f.expr.args[0], a); assert.deepEqual(f.expr.args[1], e); }); @@ -117,7 +117,7 @@ describe('FunctionAssignmentNode', function() { assert(e instanceof FunctionAssignmentNode); assert.deepEqual(e, d); assert.notStrictEqual(e, d); - assert.notStrictEqual(e.expr, d.expr); + assert.strictEqual(e.expr, d.expr); }); it ('should stringify a FunctionAssignmentNode', function () { diff --git a/test/expression/node/FunctionNode.test.js b/test/expression/node/FunctionNode.test.js index e4c2cf104..c41bba3b1 100644 --- a/test/expression/node/FunctionNode.test.js +++ b/test/expression/node/FunctionNode.test.js @@ -107,7 +107,7 @@ describe('FunctionNode', function() { return node instanceof SymbolNode && node.name == 'x' ? g : node; }); - assert.strictEqual(h, f); + assert.notStrictEqual(h, f); assert.deepEqual(h.args[0].args[0], g); assert.deepEqual(h.args[0].args[1], b); assert.deepEqual(h.name, 'multiply'); @@ -127,7 +127,7 @@ describe('FunctionNode', function() { return node; }); - assert.strictEqual(f, d); + assert.notStrictEqual(f, d); assert.deepEqual(f.name, 'subtract'); }); @@ -152,25 +152,25 @@ describe('FunctionNode', function() { var d = new FunctionNode('add', [b, c]); var count = 0; - d.traverse(function (node, index, parent) { + d.traverse(function (node, path, parent) { count++; switch(count) { case 1: assert.strictEqual(node, d); - assert.strictEqual(index, null); + assert.strictEqual(path, null); assert.strictEqual(parent, null); break; case 2: assert.strictEqual(node, b); - assert.strictEqual(index, 'args.0'); + assert.strictEqual(path, 'args.0'); assert.strictEqual(parent, d); break; case 3: assert.strictEqual(node, c); - assert.strictEqual(index, 'args.1'); + assert.strictEqual(path, 'args.1'); assert.strictEqual(parent, d); break; } @@ -190,8 +190,9 @@ describe('FunctionNode', function() { assert.deepEqual(e, d); assert.notStrictEqual(e, d); assert.equal(e.name, d.name); - assert.notStrictEqual(e.args[0], d.args[0]); - assert.notStrictEqual(e.args[1], d.args[1]); + assert.notStrictEqual(e.args, d.args); + assert.strictEqual(e.args[0], d.args[0]); + assert.strictEqual(e.args[1], d.args[1]); }); it ('should stringify a FunctionNode', function () { diff --git a/test/expression/node/IndexNode.test.js b/test/expression/node/IndexNode.test.js index a9b5c77af..cea798ab2 100644 --- a/test/expression/node/IndexNode.test.js +++ b/test/expression/node/IndexNode.test.js @@ -143,7 +143,7 @@ describe('IndexNode', function() { return node instanceof SymbolNode ? e : node; }); - assert.strictEqual(f, n); + assert.notStrictEqual(f, n); assert.deepEqual(f.object, e); assert.deepEqual(f.ranges[0], b); assert.deepEqual(f.ranges[1], c); @@ -160,7 +160,7 @@ describe('IndexNode', function() { return node instanceof ConstantNode && node.value == '1' ? e : node; }); - assert.strictEqual(f, n); + assert.notStrictEqual(f, n); assert.deepEqual(f.object, a); assert.deepEqual(f.ranges[0], b); assert.deepEqual(f.ranges[1], e); @@ -190,9 +190,10 @@ describe('IndexNode', function() { assert(d instanceof IndexNode); assert.deepEqual(d, n); assert.notStrictEqual(d, n); - assert.notStrictEqual(d.object, n.object); - assert.notStrictEqual(d.ranges[0], n.ranges[0]); - assert.notStrictEqual(d.ranges[1], n.ranges[1]); + assert.strictEqual(d.object, n.object); + assert.notStrictEqual(d.ranges, n.ranges); + assert.strictEqual(d.ranges[0], n.ranges[0]); + assert.strictEqual(d.ranges[1], n.ranges[1]); }); it ('should stringify an IndexNode', function () { diff --git a/test/expression/node/Node.test.js b/test/expression/node/Node.test.js index a3368abd3..e398affba 100644 --- a/test/expression/node/Node.test.js +++ b/test/expression/node/Node.test.js @@ -7,7 +7,10 @@ var Node = require('../../../lib/expression/node/Node'); describe('Node', function() { function MyNode () {} MyNode.prototype = new Node(); - MyNode.prototype._traverse = function () {}; + MyNode.prototype.forEach = function () {}; + MyNode.prototype.map = function () { + return new MyNode(); + }; it ('should create a Node', function () { var n = new Node(); @@ -40,7 +43,7 @@ describe('Node', function() { c = a.transform(function (node) { return node; }); - assert.strictEqual(c, a); + assert.notStrictEqual(c, a); }); it ('should transform a Node using a replacement function', function () { @@ -53,11 +56,11 @@ describe('Node', function() { assert.strictEqual(c, b); }); - it ('should clone a Node', function () { - var a = new Node(); - var b = a.clone(); - assert.deepEqual(a, b); - assert.notStrictEqual(a, b); + it ('should throw an error when cloning a Node interface', function () { + assert.throws(function () { + var a = new Node(); + a.clone(); + }, /Cannot clone a Node interface/); }); it ('should test whether an object is a Node', function () { diff --git a/test/expression/node/OperatorNode.test.js b/test/expression/node/OperatorNode.test.js index 5d7ea964e..c3919f042 100644 --- a/test/expression/node/OperatorNode.test.js +++ b/test/expression/node/OperatorNode.test.js @@ -76,7 +76,8 @@ describe('OperatorNode', function() { return node instanceof SymbolNode && node.name == 'x' ? f : node; }); - assert.strictEqual(g, e); + assert.notStrictEqual(g, e); + assert.notStrictEqual(g.args[0], e.args[0]); assert.strictEqual(g.args[0].args[0], f); assert.deepEqual(g.args[0].args[1], b); assert.deepEqual(g.args[1], f); @@ -107,8 +108,9 @@ describe('OperatorNode', function() { assert(d instanceof OperatorNode); assert.deepEqual(d, c); assert.notStrictEqual(d, c); - assert.notStrictEqual(d.args[0], c.args[0]); - assert.notStrictEqual(d.args[1], c.args[1]); + assert.notStrictEqual(d.args, c.args); + assert.strictEqual(d.args[0], c.args[0]); + assert.strictEqual(d.args[1], c.args[1]); }); it ('should stringify an OperatorNode', function () { diff --git a/test/expression/node/RangeNode.test.js b/test/expression/node/RangeNode.test.js index ccd11833a..2bb3946f4 100644 --- a/test/expression/node/RangeNode.test.js +++ b/test/expression/node/RangeNode.test.js @@ -69,7 +69,7 @@ describe('RangeNode', function() { return node instanceof ConstantNode && node.value == '0' ? e : node; }); - assert.strictEqual(f, n); + assert.notStrictEqual(f, n); assert.deepEqual(f.start, e); assert.deepEqual(f.end, end); assert.deepEqual(f.step, step); @@ -86,7 +86,7 @@ describe('RangeNode', function() { return node instanceof ConstantNode && node.value == '10' ? e : node; }); - assert.strictEqual(f, n); + assert.notStrictEqual(f, n); assert.deepEqual(f.start, start); assert.deepEqual(f.end, e); assert.deepEqual(f.step, step); @@ -103,7 +103,7 @@ describe('RangeNode', function() { return node instanceof ConstantNode && node.value == '2' ? e : node; }); - assert.strictEqual(f, n); + assert.notStrictEqual(f, n); assert.deepEqual(f.start, start); assert.deepEqual(f.end, end); assert.deepEqual(f.step, e); @@ -119,7 +119,7 @@ describe('RangeNode', function() { return node instanceof ConstantNode && node.value == '10' ? e : node; }); - assert.strictEqual(f, n); + assert.notStrictEqual(f, n); assert.deepEqual(f.start, start); assert.deepEqual(f.end, e); }); @@ -148,9 +148,9 @@ describe('RangeNode', function() { assert.deepEqual(d, c); assert.notStrictEqual(d, c); - assert.notStrictEqual(d.start, c.start); - assert.notStrictEqual(d.end, c.end); - assert.notStrictEqual(d.step, c.step); + assert.strictEqual(d.start, c.start); + assert.strictEqual(d.end, c.end); + assert.strictEqual(d.step, c.step); }); it ('should clone a RangeNode without step', function () { @@ -163,8 +163,8 @@ describe('RangeNode', function() { assert(d instanceof RangeNode); assert.deepEqual(d, c); assert.notStrictEqual(d, c); - assert.notStrictEqual(d.start, c.start); - assert.notStrictEqual(d.end, c.end); + assert.strictEqual(d.start, c.start); + assert.strictEqual(d.end, c.end); assert.strictEqual(d.step, c.step); assert.strictEqual(d.step, null); }); diff --git a/test/expression/node/UpdateNode.test.js b/test/expression/node/UpdateNode.test.js index 4df9bf42d..e5cb74049 100644 --- a/test/expression/node/UpdateNode.test.js +++ b/test/expression/node/UpdateNode.test.js @@ -164,7 +164,7 @@ describe('UpdateNode', function() { return node instanceof SymbolNode && node.name == 'x' ? e : node; }); - assert.strictEqual(f, n); + assert.notStrictEqual(f, n); assert.deepEqual(f.index.object, a); assert.deepEqual(f.index.ranges[0], b); assert.deepEqual(f.index.ranges[1], e); @@ -185,7 +185,7 @@ describe('UpdateNode', function() { return node instanceof ConstantNode && node.value == '3' ? e : node; }); - assert.strictEqual(g, n); + assert.notStrictEqual(g, n); assert.deepEqual(g.index, i); assert.deepEqual(g.index.object, a); assert.deepEqual(g.index.ranges[0], b); @@ -225,8 +225,8 @@ describe('UpdateNode', function() { assert(e instanceof UpdateNode); assert.deepEqual(e, d); assert.notStrictEqual(e, d); - assert.notStrictEqual(e.index, d.index); - assert.notStrictEqual(e.expr, d.expr); + assert.strictEqual(e.index, d.index); + assert.strictEqual(e.expr, d.expr); }); it ('should stringify an UpdateNode', function () {