NaryExpression.js 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  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 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, base = require("./Expression"), proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
  15. // DEPENDENCIES
  16. var ConstantExpression = require("./ConstantExpression");
  17. // PROTOTYPE MEMBERS
  18. proto.evaluate = undefined; // evaluate(doc){ ... defined by inheritor ... }
  19. proto.getOpName = function getOpName(doc){
  20. throw new Error("NOT IMPLEMENTED BY INHERITOR");
  21. };
  22. proto.optimize = function optimize(){
  23. var constsFound = 0,
  24. stringsFound = 0;
  25. for (var i = 0, l = this.operands.length; i < l; i++) {
  26. var optimizedExpr = this.operands[i].optimize();
  27. if (optimizedExpr instanceof ConstantExpression) {
  28. constsFound++;
  29. if (typeof(optimizedExpr.value) == "string") stringsFound++;
  30. }
  31. this.operands[i] = optimizedExpr;
  32. }
  33. // If all the operands are constant, we can replace this expression with a constant. We can find the value by evaluating this expression over a NULL Document because evaluating the ExpressionConstant never refers to the argument Document.
  34. if (constsFound === l) return new ConstantExpression(this.evaluate());
  35. // If there are any strings, we can't re-arrange anything, so stop now. LATER: we could concatenate adjacent strings as a special case.
  36. if (stringsFound) return this;
  37. // If there's no more than one constant, then we can't do any constant folding, so don't bother going any further.
  38. if (constsFound <= 1) return this;
  39. // If the operator isn't commutative or associative, there's nothing more we can do. We test that by seeing if we can get a factory; if we can, we can use it to construct a temporary expression which we'll evaluate to collapse as many constants as we can down to a single one.
  40. var IExpression = this.getFactory();
  41. if (!(IExpression instanceof Function)) return this;
  42. // Create a new Expression that will be the replacement for this one. We actually create two: one to hold constant expressions, and one to hold non-constants.
  43. // Once we've got these, we evaluate the constant expression to produce a single value, as above. We then add this operand to the end of the non-constant expression, and return that.
  44. var expr = new IExpression(),
  45. constExpr = new IExpression();
  46. for (i = 0; i < l; ++i) {
  47. var operandExpr = this.operands[i];
  48. if (operandExpr instanceof ConstantExpression) {
  49. constExpr.addOperand(operandExpr);
  50. } else {
  51. // If the child operand is the same type as this, then we can extract its operands and inline them here because we already know this is commutative and associative because it has a factory. We can detect sameness of the child operator by checking for equality of the factory
  52. // Note we don't have to do this recursively, because we called optimize() on all the children first thing in this call to optimize().
  53. if (!(operandExpr instanceof NaryExpression)) {
  54. expr.addOperand(operandExpr);
  55. } else {
  56. if (operandExpr.getFactory() !== IExpression) {
  57. expr.addOperand(operandExpr);
  58. } else { // same factory, so flatten
  59. for (var i2 = 0, n2 = operandExpr.operands.length; i2 < n2; ++i2) {
  60. var childOperandExpr = operandExpr.operands[i2];
  61. if (childOperandExpr instanceof ConstantExpression) {
  62. constExpr.addOperand(childOperandExpr);
  63. } else {
  64. expr.addOperand(childOperandExpr);
  65. }
  66. }
  67. }
  68. }
  69. }
  70. }
  71. if (constExpr.operands.length === 1) { // If there was only one constant, add it to the end of the expression operand vector.
  72. expr.addOperand(constExpr.operands[0]);
  73. } else if (constExpr.operands.length > 1) { // If there was more than one constant, collapse all the constants together before adding the result to the end of the expression operand vector.
  74. var pResult = constExpr.evaluate();
  75. expr.addOperand(new ConstantExpression(pResult));
  76. }
  77. return expr;
  78. };
  79. proto.addDependencies = function addDependencies(deps){
  80. for(var i = 0, l = this.operands.length; i < l; ++i)
  81. this.operands[i].addDependencies(deps);
  82. return deps;
  83. };
  84. /**
  85. * Add an operand to the n-ary expression.
  86. * @method addOperand
  87. * @param pExpression the expression to add
  88. **/
  89. proto.addOperand = function addOperand(expr) {
  90. this.operands.push(expr);
  91. };
  92. proto.getFactory = function getFactory() {
  93. return undefined;
  94. };
  95. proto.toJSON = function toJSON() {
  96. var o = {};
  97. o[this.getOpName()] = this.operands.map(function(operand){
  98. return operand.toJSON();
  99. });
  100. return o;
  101. };
  102. //TODO: proto.toBson ? DONE NOW???
  103. //TODO: proto.addToBsonObj ?
  104. //TODO: proto.addToBsonArray ?
  105. /**
  106. * Checks the current size of vpOperand; if the size equal to or greater than maxArgs, fires a user assertion indicating that this operator cannot have this many arguments.
  107. * The equal is there because this is intended to be used in addOperand() to check for the limit *before* adding the requested argument.
  108. *
  109. * @method checkArgLimit
  110. * @param maxArgs the maximum number of arguments the operator accepts
  111. **/
  112. proto.checkArgLimit = function checkArgLimit(maxArgs) {
  113. if (this.operands.length >= maxArgs) throw new Error(this.getOpName() + " only takes " + maxArgs + " operand" + (maxArgs == 1 ? "" : "s") + "; code 15993");
  114. };
  115. /**
  116. * Checks the current size of vpOperand; if the size is not equal to reqArgs, fires a user assertion indicating that this must have exactly reqArgs arguments.
  117. * This is meant to be used in evaluate(), *before* the evaluation takes place.
  118. *
  119. * @method checkArgCount
  120. * @param reqArgs the number of arguments this operator requires
  121. **/
  122. proto.checkArgCount = function checkArgCount(reqArgs) {
  123. if (this.operands.length !== reqArgs) throw new Error(this.getOpName() + ": insufficient operands; " + reqArgs + " required, only got " + this.operands.length + "; code 15997");
  124. };