NaryExpression.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  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. var NaryExpression = module.exports = function NaryExpression(){
  12. if (arguments.length !== 0) throw new Error("Zero args expected");
  13. this.operands = [];
  14. base.call(this);
  15. }, klass = NaryExpression, base = Expression, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
  16. klass.parse = function(SubClass) {
  17. return function parse(expr, vps) {
  18. var outExpr = new SubClass(),
  19. args = NaryExpression.parseArguments(expr, vps);
  20. outExpr.validateArguments(args);
  21. outExpr.operands = args;
  22. return outExpr;
  23. };
  24. };
  25. klass.parseArguments = function(exprElement, vps) {
  26. var out = [];
  27. if(exprElement instanceof Array) {
  28. for(var ii = 0; ii < exprElement.length; ii++) {
  29. out.push(Expression.parseOperand(exprElement[ii], vps));
  30. }
  31. } else {
  32. out.push(Expression.parseOperand(exprElement, vps));
  33. }
  34. return out;
  35. };
  36. function partitionBy(fn, coll) {
  37. var ret = {pass:[],
  38. fail:[]};
  39. coll.forEach(function(x) {
  40. if(fn(x)) {
  41. ret.pass.push(x);
  42. } else {
  43. ret.fail.push(x);
  44. }
  45. });
  46. return ret;
  47. }
  48. // DEPENDENCIES
  49. var ConstantExpression = require("./ConstantExpression");
  50. // PROTOTYPE MEMBERS
  51. proto.evaluate = undefined; // evaluate(doc){ ... defined by inheritor ... }
  52. proto.getOpName = function getOpName(doc){
  53. throw new Error("NOT IMPLEMENTED BY INHERITOR");
  54. };
  55. proto.optimize = function optimize(){
  56. var n = this.operands.length,
  57. constantCount = 0;
  58. for(var ii = 0; ii < n; ii++) {
  59. if(this.operands[ii] instanceof ConstantExpression) {
  60. constantCount++;
  61. } else {
  62. this.operands[ii] = this.operands[ii].optimize();
  63. }
  64. }
  65. if(constantCount === n) {
  66. return new ConstantExpression(this.evaluateInternal({}));
  67. }
  68. if(!this.isAssociativeAndCommutative) {
  69. return this;
  70. }
  71. // Flatten and inline nested operations of the same type
  72. var similar = partitionBy(function(x){ return x.getOpName() === this.getOpName();}, this.operands);
  73. this.operands = similar.fail;
  74. similar.pass.forEach(function(x){
  75. this.operands.concat(x.operands);
  76. });
  77. // Partial constant folding
  78. var constantOperands = partitionBy(function(x) {return x instanceof ConstantExpression;}, this.operands);
  79. this.operands = constantOperands.pass;
  80. this.operands = [new ConstantExpression(this.evaluateInternal({}))].concat(constantOperands.fail);
  81. return this;
  82. };
  83. proto.addDependencies = function addDependencies(deps){
  84. for(var i = 0, l = this.operands.length; i < l; ++i)
  85. this.operands[i].addDependencies(deps);
  86. };
  87. /**
  88. * Add an operand to the n-ary expression.
  89. * @method addOperand
  90. * @param pExpression the expression to add
  91. **/
  92. proto.addOperand = function addOperand(expr) {
  93. this.operands.push(expr);
  94. };
  95. proto.serialize = function serialize() {
  96. var ret = {}, subret = [];
  97. for(var ii = 0; ii < this.operands.length; ii++) {
  98. subret.push(this.operands[ii].serialize());
  99. }
  100. ret[this.getOpName()] = subret;
  101. return ret;
  102. };
  103. proto.fixedArity = function(nargs) {
  104. this.nargs = nargs;
  105. };
  106. proto.validateArguments = function(args) {
  107. if(this.nargs !== args.length) {
  108. throw new Error("Expression " + this.getOpName() + " takes exactly " + this.nargs + " arguments. " + args.length + " were passed in.");
  109. }
  110. };