NaryExpressionT.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. "use strict";
  2. /**
  3. * The base class for all n-ary `Expression`s
  4. * @class NaryExpression
  5. * @namespace mungedb-aggregate.pipeline.expressions
  6. * @module mungedb-aggregate
  7. * @extends mungedb-aggregate.pipeline.expressions.Expression
  8. * @constructor
  9. **/
  10. var Expression = require("./Expression"),
  11. Variables = require("./Variables");
  12. var NaryExpressionT = module.exports = function NaryExpressionT(SubClass) {
  13. var NaryExpression = function NaryExpression(){
  14. if (arguments.length !== 0) throw new Error("Zero args expected");
  15. this.operands = [];
  16. base.call(this);
  17. }, klass = NaryExpression, base = Expression, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
  18. klass.parseArguments = function(exprElement, vps) {
  19. var out = [];
  20. if(exprElement instanceof Array) {
  21. for(var ii = 0; ii < exprElement.length; ii++) {
  22. out.push(Expression.parseOperand(exprElement[ii], vps));
  23. }
  24. } else {
  25. out.push(Expression.parseOperand(exprElement, vps));
  26. }
  27. return out;
  28. };
  29. klass.parse = function(expr, vps) {
  30. var outExpr = new SubClass(),
  31. args = NaryExpression.parseArguments(expr, vps);
  32. outExpr.validateArguments(args);
  33. outExpr.operands = args;
  34. return outExpr;
  35. };
  36. proto.optimize = function optimize(){
  37. var n = this.operands.length,
  38. constCount = 0;
  39. // optimize sub-expressions and count constants
  40. for(var ii = 0; ii < n; ii++) {
  41. var optimized = this.operands[ii].optimize();
  42. // substitute the optimized expression
  43. this.operands[ii] = optimized;
  44. // check to see if the result was a constant
  45. if(optimized instanceof ConstantExpression) {
  46. constCount++;
  47. }
  48. }
  49. // If all the operands are constant, we can replace this expression with a constant. Using
  50. // an empty Variables since it will never be accessed.
  51. if(constCount === n) {
  52. var emptyVars = new Variables(),
  53. result = this.evaluateInternal(emptyVars),
  54. replacement = new ConstantExpression(result);
  55. return replacement;
  56. }
  57. // Remaining optimizations are only for associative and commutative expressions.
  58. if(!this.isAssociativeAndCommutative()) {
  59. return this;
  60. }
  61. // Process vpOperand to split it into constant and nonconstant vectors.
  62. // This can leave vpOperand in an invalid state that is cleaned up after the loop.
  63. var constExprs = [],
  64. nonConstExprs = [];
  65. for(var i=0; i<this.operands.length; i++) { // NOTE: vpOperand grows in loop
  66. var expr = this.operands[i];
  67. if(expr instanceof ConstantExpression) {
  68. constExprs.push(expr);
  69. } else {
  70. // If the child operand is the same type as this, then we can
  71. // extract its operands and inline them here because we know
  72. // this is commutative and associative. We detect sameness of
  73. // the child operator by checking for equality of the opNames
  74. var nary = expr;
  75. if(!(nary instanceof NaryExpression) || nary.getOpName() !== this.getOpName) {
  76. nonConstExprs.push(expr);
  77. } else {
  78. // same expression, so flatten by adding to vpOperand which
  79. // will be processed later in this loop.
  80. for(var j=0; j<nary.operands.length; j++) {
  81. this.operands.push(nary.operands[j]);
  82. }
  83. }
  84. }
  85. }
  86. // collapse all constant expressions (if any)
  87. var constValue;
  88. if(constExprs.length > 0) {
  89. this.operands = constExprs;
  90. var emptyVars = new Variables();
  91. constValue = this.evaluateInternal(emptyVars);
  92. }
  93. // now set the final expression list with constant (if any) at the end
  94. this.operands = nonConstExprs;
  95. if (constExprs.length > 0) {
  96. this.operands.push(new ConstantExpression(constValue));
  97. }
  98. return this;
  99. };
  100. // DEPENDENCIES
  101. var ConstantExpression = require("./ConstantExpression");
  102. proto.getOpName = function getOpName(doc){
  103. throw new Error("NOT IMPLEMENTED BY INHERITOR");
  104. };
  105. proto.addDependencies = function addDependencies(deps, path){
  106. for(var i = 0, l = this.operands.length; i < l; ++i)
  107. this.operands[i].addDependencies(deps);
  108. };
  109. /**
  110. * Add an operand to the n-ary expression.
  111. * @method addOperand
  112. * @param pExpression the expression to add
  113. **/
  114. proto.addOperand = function addOperand(expr) {
  115. this.operands.push(expr);
  116. };
  117. proto.isAssociativeAndCommutative = function isAssociativeAndCommutative() {
  118. return false;
  119. };
  120. proto.serialize = function serialize(explain) {
  121. var nOperand = this.operands.length,
  122. array = [];
  123. for(var i=0; i<nOperand; i++){
  124. array.push(this.operands[i].serialize(explain));
  125. }
  126. var obj = {};
  127. obj[this.getOpName()] = array;
  128. return obj;
  129. };
  130. proto.validateArguments = function(args) {
  131. if(this.nargs !== args.length) {
  132. throw new Error("Expression " + this.getOpName() + " takes exactly " + this.nargs + " arguments. " + args.length + " were passed in.");
  133. }
  134. };
  135. return NaryExpression;
  136. };