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