CompareExpression.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. var CompareExpression = module.exports = (function(){
  2. // CONSTRUCTOR
  3. /**
  4. * Generic comparison expression that gets used for $eq, $ne, $lt, $lte, $gt, $gte, and $cmp.
  5. *
  6. * @class CompareExpression
  7. * @namespace munge.pipeline.expressions
  8. * @module munge
  9. * @constructor
  10. **/
  11. var klass = module.exports = CompareExpression = function CompareExpression(cmpOp) {
  12. if(arguments.length !== 1) throw new Error("args expected: cmpOp");
  13. this.cmpOp = cmpOp;
  14. base.call(this);
  15. }, base = require("./NaryExpression"), proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
  16. // DEPENDENCIES
  17. var Value = require("../Value"),
  18. Expression = require("./Expression"),
  19. ConstantExpression = require("./ConstantExpression"),
  20. FieldPathExpression = require("./FieldPathExpression"),
  21. FieldRangeExpression = require("./FieldRangeExpression");
  22. // NESTED CLASSES
  23. /**
  24. * Lookup table for truth value returns
  25. *
  26. * @param truthValues truth value for -1, 0, 1
  27. * @param reverse reverse comparison operator
  28. * @param name string name
  29. **/
  30. var CmpLookup = (function(){ // emulating a struct
  31. // CONSTRUCTOR
  32. var klass = function CmpLookup(truthValues, reverse, name) {
  33. if(arguments.length !== 3) throw new Error("args expected: truthValues, reverse, name");
  34. this.truthValues = truthValues;
  35. this.reverse = reverse;
  36. this.name = name;
  37. }, base = Object, proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
  38. return klass;
  39. })();
  40. // PRIVATE STATIC MEMBERS
  41. /** a table of cmp type lookups to truth values **/
  42. var cmpLookupMap = [ //NOTE: converted from this Array to a Dict/Object below using CmpLookup#name as the key
  43. // -1 0 1 reverse name (taking advantage of the fact that our 'enums' are strings below)
  44. new CmpLookup([false, true, false], Expression.CmpOp.EQ, Expression.CmpOp.EQ),
  45. new CmpLookup([true, false, true], Expression.CmpOp.NE, Expression.CmpOp.NE),
  46. new CmpLookup([false, false, true], Expression.CmpOp.LT, Expression.CmpOp.GT),
  47. new CmpLookup([false, true, true], Expression.CmpOp.LTE, Expression.CmpOp.GTE),
  48. new CmpLookup([true, false, false], Expression.CmpOp.GT, Expression.CmpOp.LT),
  49. new CmpLookup([true, true, false], Expression.CmpOp.GTE, Expression.CmpOp.LTE),
  50. new CmpLookup([false, false, false], Expression.CmpOp.CMP, Expression.CmpOp.CMP)
  51. ].reduce(function(r,o){r[o.name]=o;return r;},{});
  52. // PROTOTYPE MEMBERS
  53. proto.addOperand = function addOperand(expr) {
  54. this.checkArgLimit(2);
  55. base.prototype.addOperand.call(this, expr);
  56. };
  57. proto.evaluate = function evaluate(doc) {
  58. this.checkArgCount(2);
  59. var left = this.operands[0].evaluate(doc),
  60. right = this.operands[1].evaluate(doc),
  61. cmp = Expression.signum(Value.compare(left, right));
  62. if (this.cmpOp == Expression.CmpOp.CMP) return cmp;
  63. return cmpLookupMap[this.cmpOp].truthValues[cmp + 1] || false;
  64. };
  65. proto.optimize = function optimize(){
  66. var expr = base.prototype.optimize.call(this); // first optimize the comparison operands
  67. if (!(expr instanceof CompareExpression)) return expr; // if no longer a comparison, there's nothing more we can do.
  68. // check to see if optimizing comparison operator is supported // CMP and NE cannot use ExpressionFieldRange which is what this optimization uses
  69. var newOp = this.cmpOp;
  70. if (newOp == Expression.CmpOp.CMP || newOp == Expression.CmpOp.NE) return expr;
  71. // There's one localized optimization we recognize: a comparison between a field and a constant. If we recognize that pattern, replace it with an ExpressionFieldRange.
  72. // When looking for this pattern, note that the operands could appear in any order. If we need to reverse the sense of the comparison to put it into the required canonical form, do so.
  73. var leftExpr = this.operands[0],
  74. rightExpr = this.operands[1];
  75. var fieldPathExpr, constantExpr;
  76. if (leftExpr instanceof FieldPathExpression) {
  77. fieldPathExpr = leftExpr;
  78. if (!(rightExpr instanceof ConstantExpression)) return expr; // there's nothing more we can do
  79. constantExpr = rightExpr;
  80. } else {
  81. // if the first operand wasn't a path, see if it's a constant
  82. if (!(leftExpr instanceof ConstantExpression)) return expr; // there's nothing more we can do
  83. constantExpr = leftExpr;
  84. // the left operand was a constant; see if the right is a path
  85. if (!(rightExpr instanceof FieldPathExpression)) return expr; // there's nothing more we can do
  86. fieldPathExpr = rightExpr;
  87. // these were not in canonical order, so reverse the sense
  88. newOp = cmpLookupMap[newOp].reverse;
  89. }
  90. return new FieldRangeExpression(fieldPathExpr, newOp, constantExpr.getValue());
  91. };
  92. proto.getOpName = function getOpName(){
  93. return this.cmpOp;
  94. };
  95. return klass;
  96. })();