1 /**
  2  * @fileoverview A class representing operations on binary expressions.
  3  */
  4 
  5 goog.provide('xrx.xpath.BinaryExpr');
  6 
  7 goog.require('xrx.xpath.DataType');
  8 goog.require('xrx.xpath.Expr');
  9 goog.require('xrx.node');
 10 
 11 
 12 
 13 xrx.xpath.BinaryExpr = function(op, left, right) {
 14   var opCast = /** @type {!xrx.xpath.BinaryExpr.Op_} */ (op);
 15   xrx.xpath.Expr.call(this, opCast.dataType_);
 16 
 17   /**
 18    * @private
 19    * @type {!xrx.xpath.BinaryExpr.Op_}
 20    */
 21   this.op_ = opCast;
 22 
 23   /**
 24    * @private
 25    * @type {!xrx.xpath.Expr}
 26    */
 27   this.left_ = left;
 28 
 29   /**
 30    * @private
 31    * @type {!xrx.xpath.Expr}
 32    */
 33   this.right_ = right;
 34 
 35   this.setNeedContextPosition(left.doesNeedContextPosition() ||
 36       right.doesNeedContextPosition());
 37   this.setNeedContextNode(left.doesNeedContextNode() ||
 38       right.doesNeedContextNode());
 39 
 40   // Optimize [@id="foo"] and [@name="bar"]
 41   if (this.op_ == xrx.xpath.BinaryExpr.Op.EQUAL) {
 42     if (!right.doesNeedContextNode() && !right.doesNeedContextPosition() &&
 43         right.getDataType() != xrx.xpath.DataType.NODESET &&
 44         right.getDataType() != xrx.xpath.DataType.VOID && left.getQuickAttr()) {
 45       this.setQuickAttr({
 46         name: left.getQuickAttr().name,
 47         valueExpr: right});
 48     } else if (!left.doesNeedContextNode() && !left.doesNeedContextPosition() &&
 49         left.getDataType() != xrx.xpath.DataType.NODESET &&
 50         left.getDataType() != xrx.xpath.DataType.VOID && right.getQuickAttr()) {
 51       this.setQuickAttr({
 52         name: right.getQuickAttr().name,
 53         valueExpr: left});
 54     }
 55   }
 56 };
 57 goog.inherits(xrx.xpath.BinaryExpr, xrx.xpath.Expr);
 58 
 59 
 60 /**
 61  * Performs comparison between the left hand side and the right hand side.
 62  *
 63  * @private
 64  * @param {function((string|number|boolean), (string|number|boolean))}
 65  *        comp A comparison function that takes two parameters.
 66  * @param {!xrx.xpath.Expr} lhs The left hand side of the expression.
 67  * @param {!xrx.xpath.Expr} rhs The right hand side of the expression.
 68  * @param {!xrx.xpath.Context} ctx The context to perform the comparison in.
 69  * @param {boolean=} opt_equChk Whether the comparison checks for equality.
 70  * @return {boolean} True if comp returns true, false otherwise.
 71  */
 72 xrx.xpath.BinaryExpr.compare_ = function(comp, lhs, rhs, ctx, opt_equChk) {
 73   var left = lhs.evaluate(ctx);
 74   var right = rhs.evaluate(ctx);
 75   var lIter, rIter, lNode, rNode;
 76   if (left instanceof xrx.xpath.NodeSet && right instanceof xrx.xpath.NodeSet) {
 77     lIter = left.iterator();
 78     for (lNode = lIter.next(); lNode; lNode = lIter.next()) {
 79       rIter = right.iterator();
 80       for (rNode = rIter.next(); rNode; rNode = rIter.next()) {
 81         if (comp(xrx.node.getValueAsString(lNode),
 82             xrx.node.getValueAsString(rNode))) {
 83           return true;
 84         }
 85       }
 86     }
 87     return false;
 88   }
 89   if ((left instanceof xrx.xpath.NodeSet) ||
 90       (right instanceof xrx.xpath.NodeSet)) {
 91     var nodeset, primitive;
 92     if ((left instanceof xrx.xpath.NodeSet)) {
 93       nodeset = left, primitive = right;
 94     } else {
 95       nodeset = right, primitive = left;
 96     }
 97     var iter = nodeset.iterator();
 98     var type = typeof primitive;
 99     for (var node = iter.next(); node; node = iter.next()) {
100       var stringValue;
101       switch (type) {
102         case 'number':
103           stringValue = xrx.node.getValueAsNumber(node);
104           break;
105         case 'boolean':
106           stringValue = xrx.node.getValueAsBool(node);
107           break;
108         case 'string':
109           stringValue = xrx.node.getValueAsString(node);
110           break;
111         default:
112           throw Error('Illegal primitive type for comparison.');
113       }
114       if (comp(stringValue,
115           /** @type {(string|number|boolean)} */ (primitive))) {
116         return true;
117       }
118     }
119     return false;
120   }
121   if (opt_equChk) {
122     if (typeof left == 'boolean' || typeof right == 'boolean') {
123       return comp(!!left, !!right);
124     }
125     if (typeof left == 'number' || typeof right == 'number') {
126       return comp(+left, +right);
127     }
128     return comp(left, right);
129   }
130   return comp(+left, +right);
131 };
132 
133 
134 /**
135  * @override
136  * @return {(boolean|number)} The boolean or number result.
137  */
138 xrx.xpath.BinaryExpr.prototype.evaluate = function(ctx) {
139   return this.op_.evaluate_(this.left_, this.right_, ctx);
140 };
141 
142 
143 /**
144  * @override
145  */
146 xrx.xpath.BinaryExpr.prototype.toString = function() {
147   var text = 'Binary Expression: ' + this.op_;
148   text += xrx.xpath.Expr.indent(this.left_);
149   text += xrx.xpath.Expr.indent(this.right_);
150   return text;
151 };
152 
153 
154 
155 /**
156  * A binary operator.
157  *
158  * @param {string} opString The operator string.
159  * @param {number} precedence The precedence when evaluated.
160  * @param {!xrx.xpath.DataType} dataType The dataType to return when evaluated.
161  * @param {function(!xrx.xpath.Expr, !xrx.xpath.Expr, !xrx.xpath.Context)}
162  *         evaluate An evaluation function.
163  * @constructor
164  * @private
165  */
166 xrx.xpath.BinaryExpr.Op_ = function(opString, precedence, dataType, evaluate) {
167 
168   /**
169    * @private
170    * @type {string}
171    */
172   this.opString_ = opString;
173 
174   /**
175    * @private
176    * @type {number}
177    */
178   this.precedence_ = precedence;
179 
180   /**
181    * @private
182    * @type {!xrx.xpath.DataType}
183    */
184   this.dataType_ = dataType;
185 
186   /**
187    * @private
188    * @type {function(!xrx.xpath.Expr, !xrx.xpath.Expr, !xrx.xpath.Context)}
189    */
190   this.evaluate_ = evaluate;
191 };
192 
193 
194 /**
195  * Returns the precedence for the operator.
196  *
197  * @return {number} The precedence.
198  */
199 xrx.xpath.BinaryExpr.Op_.prototype.getPrecedence = function() {
200   return this.precedence_;
201 };
202 
203 
204 /**
205  * @override
206  */
207 xrx.xpath.BinaryExpr.Op_.prototype.toString = function() {
208   return this.opString_;
209 };
210 
211 
212 /**
213  * A mapping from operator strings to operator objects.
214  *
215  * @private
216  * @type {!Object.<string, !xrx.xpath.BinaryExpr.Op>}
217  */
218 xrx.xpath.BinaryExpr.stringToOpMap_ = {};
219 
220 
221 /**
222  * Creates a binary operator.
223  *
224  * @param {string} opString The operator string.
225  * @param {number} precedence The precedence when evaluated.
226  * @param {!xrx.xpath.DataType} dataType The dataType to return when evaluated.
227  * @param {function(!xrx.xpath.Expr, !xrx.xpath.Expr, !xrx.xpath.Context)}
228  *         evaluate An evaluation function.
229  * @return {!xrx.xpath.BinaryExpr.Op} A binary expression operator.
230  * @private
231  */
232 xrx.xpath.BinaryExpr.createOp_ = function(opString, precedence, dataType,
233     evaluate) {
234   if (opString in xrx.xpath.BinaryExpr.stringToOpMap_) {
235     throw new Error('Binary operator already created: ' + opString);
236   }
237   // The upcast and then downcast for the JSCompiler.
238   var op = /** @type {!Object} */ (new xrx.xpath.BinaryExpr.Op_(
239       opString, precedence, dataType, evaluate));
240   op = /** @type {!xrx.xpath.BinaryExpr.Op} */ (op);
241   xrx.xpath.BinaryExpr.stringToOpMap_[op.toString()] = op;
242   return op;
243 };
244 
245 
246 /**
247  * Returns the operator with this opString or null if none.
248  *
249  * @param {string} opString The opString.
250  * @return {!xrx.xpath.BinaryExpr.Op} The operator.
251  */
252 xrx.xpath.BinaryExpr.getOp = function(opString) {
253   return xrx.xpath.BinaryExpr.stringToOpMap_[opString] || null;
254 };
255 
256 
257 /**
258  * Binary operator enumeration.
259  *
260  * @enum {{getPrecedence: function(): number}}
261  */
262 xrx.xpath.BinaryExpr.Op = {
263   DIV: xrx.xpath.BinaryExpr.createOp_('div', 6, xrx.xpath.DataType.NUMBER,
264       function(left, right, ctx) {
265         return left.asNumber(ctx) / right.asNumber(ctx);
266       }),
267   MOD: xrx.xpath.BinaryExpr.createOp_('mod', 6, xrx.xpath.DataType.NUMBER,
268       function(left, right, ctx) {
269         return left.asNumber(ctx) % right.asNumber(ctx);
270       }),
271   MULT: xrx.xpath.BinaryExpr.createOp_('*', 6, xrx.xpath.DataType.NUMBER,
272       function(left, right, ctx) {
273         return left.asNumber(ctx) * right.asNumber(ctx);
274       }),
275   PLUS: xrx.xpath.BinaryExpr.createOp_('+', 5, xrx.xpath.DataType.NUMBER,
276       function(left, right, ctx) {
277         return left.asNumber(ctx) + right.asNumber(ctx);
278       }),
279   MINUS: xrx.xpath.BinaryExpr.createOp_('-', 5, xrx.xpath.DataType.NUMBER,
280       function(left, right, ctx) {
281         return left.asNumber(ctx) - right.asNumber(ctx);
282       }),
283   LESSTHAN: xrx.xpath.BinaryExpr.createOp_('<', 4, xrx.xpath.DataType.BOOLEAN,
284       function(left, right, ctx) {
285         return xrx.xpath.BinaryExpr.compare_(function(a, b) {return a < b;},
286             left, right, ctx);
287       }),
288   GREATERTHAN: xrx.xpath.BinaryExpr.createOp_('>', 4, xrx.xpath.DataType.BOOLEAN,
289       function(left, right, ctx) {
290         return xrx.xpath.BinaryExpr.compare_(function(a, b) {return a > b;},
291             left, right, ctx);
292       }),
293   LESSTHAN_EQUAL: xrx.xpath.BinaryExpr.createOp_(
294       '<=', 4, xrx.xpath.DataType.BOOLEAN,
295       function(left, right, ctx) {
296         return xrx.xpath.BinaryExpr.compare_(function(a, b) {return a <= b;},
297             left, right, ctx);
298       }),
299   GREATERTHAN_EQUAL: xrx.xpath.BinaryExpr.createOp_('>=', 4,
300       xrx.xpath.DataType.BOOLEAN, function(left, right, ctx) {
301         return xrx.xpath.BinaryExpr.compare_(function(a, b) {return a >= b;},
302             left, right, ctx);
303       }),
304   EQUAL: xrx.xpath.BinaryExpr.createOp_('=', 3, xrx.xpath.DataType.BOOLEAN,
305       function(left, right, ctx) {
306         return xrx.xpath.BinaryExpr.compare_(function(a, b) {return a == b;},
307             left, right, ctx, true);
308       }),
309   NOT_EQUAL: xrx.xpath.BinaryExpr.createOp_('!=', 3, xrx.xpath.DataType.BOOLEAN,
310       function(left, right, ctx) {
311         return xrx.xpath.BinaryExpr.compare_(function(a, b) {return a != b},
312             left, right, ctx, true);
313       }),
314   AND: xrx.xpath.BinaryExpr.createOp_('and', 2, xrx.xpath.DataType.BOOLEAN,
315       function(left, right, ctx) {
316         return left.asBool(ctx) && right.asBool(ctx);
317       }),
318   OR: xrx.xpath.BinaryExpr.createOp_('or', 1, xrx.xpath.DataType.BOOLEAN,
319       function(left, right, ctx) {
320         return left.asBool(ctx) || right.asBool(ctx);
321       })
322 };
323