NaryExpression.js 4.4 KB

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