Package pyffi :: Package object_models :: Package xml :: Module expression
[hide private]
[frames] | no frames]

Source Code for Module pyffi.object_models.xml.expression

  1  """Expression parser (for arr1, arr2, cond, and vercond xml attributes of 
  2  <add> tag).""" 
  3   
  4  # -------------------------------------------------------------------------- 
  5  # ***** BEGIN LICENSE BLOCK ***** 
  6  # 
  7  # Copyright (c) 2007-2011, Python File Format Interface 
  8  # All rights reserved. 
  9  # 
 10  # Redistribution and use in source and binary forms, with or without 
 11  # modification, are permitted provided that the following conditions 
 12  # are met: 
 13  # 
 14  #    * Redistributions of source code must retain the above copyright 
 15  #      notice, this list of conditions and the following disclaimer. 
 16  # 
 17  #    * Redistributions in binary form must reproduce the above 
 18  #      copyright notice, this list of conditions and the following 
 19  #      disclaimer in the documentation and/or other materials provided 
 20  #      with the distribution. 
 21  # 
 22  #    * Neither the name of the Python File Format Interface 
 23  #      project nor the names of its contributors may be used to endorse 
 24  #      or promote products derived from this software without specific 
 25  #      prior written permission. 
 26  # 
 27  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 28  # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
 29  # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
 30  # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 
 31  # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
 32  # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 33  # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
 34  # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
 35  # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
 36  # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
 37  # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 38  # POSSIBILITY OF SUCH DAMAGE. 
 39  # 
 40  # ***** END LICENSE BLOCK ***** 
 41  # -------------------------------------------------------------------------- 
 42   
 43  import sys # stderr (for debugging) 
44 45 -class Expression(object):
46 """This class represents an expression. 47 48 >>> class A(object): 49 ... x = False 50 ... y = True 51 >>> a = A() 52 >>> e = Expression('x || y') 53 >>> e.eval(a) 54 1 55 >>> Expression('99 & 15').eval(a) 56 3 57 >>> bool(Expression('(99&15)&&y').eval(a)) 58 True 59 >>> a.hello_world = False 60 >>> def nameFilter(s): 61 ... return 'hello_' + s.lower() 62 >>> bool(Expression('(99 &15) &&WoRlD', name_filter = nameFilter).eval(a)) 63 False 64 >>> Expression('c && d').eval(a) 65 Traceback (most recent call last): 66 ... 67 AttributeError: 'A' object has no attribute 'c' 68 >>> bool(Expression('1 == 1').eval()) 69 True 70 >>> bool(Expression('(1 == 1)').eval()) 71 True 72 >>> bool(Expression('1 != 1').eval()) 73 False 74 >>> bool(Expression('!(1 == 1)').eval()) 75 False 76 >>> bool(Expression('!((1 <= 2) && (2 <= 3))').eval()) 77 False 78 >>> bool(Expression('(1 <= 2) && (2 <= 3) && (3 <= 4)').eval()) 79 True 80 """ 81 operators = [ '==', '!=', '>=', '<=', '&&', '||', '&', '|', '-', '!', 82 '<', '>', '/', '*', '+' ]
83 - def __init__(self, expr_str, name_filter = None):
84 try: 85 left, self._op, right = self._partition(expr_str) 86 self._left = self._parse(left, name_filter) 87 self._right = self._parse(right, name_filter) 88 except: 89 print("error while parsing expression '%s'" % expr_str) 90 raise
91
92 - def eval(self, data = None):
93 """Evaluate the expression to an integer.""" 94 95 if isinstance(self._left, Expression): 96 left = self._left.eval(data) 97 elif isinstance(self._left, basestring): 98 if self._left == '""': 99 left = "" 100 else: 101 left = data 102 for part in self._left.split("."): 103 left = getattr(left, part) 104 elif self._left is None: 105 pass 106 else: 107 assert(isinstance(self._left, (int, long))) # debug 108 left = self._left 109 110 if not self._op: 111 return left 112 113 if isinstance(self._right, Expression): 114 right = self._right.eval(data) 115 elif isinstance(self._right, basestring): 116 if (not self._right) or self._right == '""': 117 right = "" 118 else: 119 right = getattr(data, self._right) 120 elif self._right is None: 121 pass 122 else: 123 assert(isinstance(self._right, (int, long))) # debug 124 right = self._right 125 126 if self._op == '==': 127 return int(left == right) 128 elif self._op == '!=': 129 return int(left != right) 130 elif self._op == '>=': 131 return int(left >= right) 132 elif self._op == '<=': 133 return int(left <= right) 134 elif self._op == '&&': 135 return int(left and right) 136 elif self._op == '||': 137 return int(left or right) 138 elif self._op == '&': 139 return left & right 140 elif self._op == '|': 141 return left | right 142 elif self._op == '-': 143 return left - right 144 elif self._op == '!': 145 return int(not(right)) 146 elif self._op == '>': 147 return int(left > right) 148 elif self._op == '<': 149 return int(left < right) 150 elif self._op == '/': 151 return int(left / right) 152 elif self._op == '*': 153 return int(left * right) 154 elif self._op == '+': 155 return left + right 156 else: 157 raise NotImplementedError("expression syntax error: operator '" + self._op + "' not implemented")
158
159 - def __str__(self):
160 """Reconstruct the expression to a string.""" 161 162 left = str(self._left) if not self._left is None else "" 163 if not self._op: return left 164 right = str(self._right) if not self._right is None else "" 165 return left + ' ' + self._op + ' ' + right
166 167 @classmethod
168 - def _parse(cls, expr_str, name_filter = None):
169 """Returns an Expression, string, or int, depending on the 170 contents of <expr_str>.""" 171 if not expr_str: 172 # empty string 173 return None 174 # brackets or operators => expression 175 if ("(" in expr_str) or (")" in expr_str): 176 return Expression(expr_str, name_filter) 177 for op in cls.operators: 178 if expr_str.find(op) != -1: 179 return Expression(expr_str, name_filter) 180 # try to convert it to an integer 181 try: 182 return int(expr_str) 183 # failed, so return the string, passed through the name filter 184 except ValueError: 185 if name_filter: 186 result = name_filter(expr_str) 187 if isinstance(result, (long, int)): 188 # XXX this is a workaround for the vercond filter 189 return result 190 else: 191 # apply name filter on each component separately 192 # (where a dot separates components) 193 return '.'.join(name_filter(comp) 194 for comp in expr_str.split(".")) 195 else: 196 return expr_str
197 198 @classmethod
199 - def _partition(cls, expr_str):
200 """Partitions expr_str. See examples below. 201 202 >>> Expression._partition('abc || efg') 203 ('abc', '||', 'efg') 204 >>> Expression._partition('abc||efg') 205 ('abc', '||', 'efg') 206 >>> Expression._partition('abcdefg') 207 ('abcdefg', '', '') 208 >>> Expression._partition(' abcdefg ') 209 ('abcdefg', '', '') 210 >>> Expression._partition(' (a | b) & c ') 211 ('a | b', '&', 'c') 212 >>> Expression._partition('(a | b)!=(b&c)') 213 ('a | b', '!=', 'b&c') 214 >>> Expression._partition('(a== b) &&(( b!=c)||d )') 215 ('a== b', '&&', '( b!=c)||d') 216 >>> Expression._partition('!(1 <= 2)') 217 ('', '!', '(1 <= 2)') 218 >>> Expression._partition('') 219 ('', '', '') 220 >>> Expression._partition('(1 == 1)') 221 ('1 == 1', '', '') 222 """ 223 # strip whitespace 224 expr_str = expr_str.strip() 225 226 # all operators have a left hand side and a right hand side 227 # except for negation, so let us deal with that case first 228 if expr_str.startswith("!"): 229 return "", "!", expr_str[1:].strip() 230 231 # check if the left hand side starts with brackets 232 # and if so, find the position of the starting bracket and the ending 233 # bracket 234 left_startpos, left_endpos = cls._scanBrackets(expr_str) 235 if left_startpos >= 0: 236 # yes, it is a bracketted expression 237 # so remove brackets and whitespace, 238 # and let that be the left hand side 239 left_str = expr_str[left_startpos+1:left_endpos].strip() 240 # if there is no next token, then just return the expression 241 # without brackets 242 if left_endpos + 1 == len(expr_str): 243 return left_str, "", "" 244 # the next token should be the operator 245 # find the position where the operator should start 246 op_startpos = left_endpos+1 247 while expr_str[op_startpos] == " ": 248 op_startpos += 1 249 # to avoid confusion between && and &, and || and |, 250 # let's first scan for operators of two characters 251 # and then for operators of one character 252 for op_endpos in xrange(op_startpos+1, op_startpos-1, -1): 253 op_str = expr_str[op_startpos:op_endpos+1] 254 if op_str in cls.operators: 255 break 256 else: 257 raise ValueError("expression syntax error: expected operator at '%s'"%expr_str[op_startpos:]) 258 else: 259 # it's not... so we need to scan for the first operator 260 for op_startpos, ch in enumerate(expr_str): 261 if ch == ' ': continue 262 if ch == '(' or ch == ')': 263 raise ValueError("expression syntax error: expected operator before '%s'"%expr_str[op_startpos:]) 264 # to avoid confusion between && and &, and || and |, 265 # let's first scan for operators of two characters 266 for op_endpos in xrange(op_startpos+1, op_startpos-1, -1): 267 op_str = expr_str[op_startpos:op_endpos+1] 268 if op_str in cls.operators: 269 break 270 else: 271 continue 272 break 273 else: 274 # no operator found, so we are done 275 left_str = expr_str.strip() 276 op_str = '' 277 right_str = '' 278 return left_str, op_str, right_str 279 # operator found! now get the left hand side 280 left_str = expr_str[:op_startpos].strip() 281 282 # now we have done the left hand side, and the operator 283 # all that is left is to process the right hand side 284 right_startpos, right_endpos = cls._scanBrackets(expr_str, op_endpos+1) 285 if right_startpos >= 0: 286 # yes, we found a bracketted expression 287 # so remove brackets and whitespace, 288 # and let that be the right hand side 289 right_str = expr_str[right_startpos+1:right_endpos].strip() 290 # check for trailing junk 291 if expr_str[right_endpos+1:] and not expr_str[right_endpos+1:] == ' ': 292 for op in cls.operators: 293 if expr_str.find(op) != -1: 294 break 295 else: 296 raise ValueError("expression syntax error: unexpected trailing characters '%s'"%expr_str[right_endpos+1:]) 297 # trailing characters contain an operator: do not remove 298 # brackets but take 299 # everything to be the right hand side (this happens for 300 # instance in '(x <= y) && (y <= z) && (x != z)') 301 right_str = expr_str[op_endpos+1:].strip() 302 else: 303 # no, so just take the whole expression as right hand side 304 right_str = expr_str[op_endpos+1:].strip() 305 # check that it is a valid expression 306 if ("(" in right_str) or (")" in right_str): 307 raise ValueError("expression syntax error: unexpected brackets in '%s'"%right_str) 308 return left_str, op_str, right_str
309 310 @staticmethod
311 - def _scanBrackets(expr_str, fromIndex = 0):
312 """Looks for matching brackets. 313 314 >>> Expression._scanBrackets('abcde') 315 (-1, -1) 316 >>> Expression._scanBrackets('()') 317 (0, 1) 318 >>> Expression._scanBrackets('(abc(def))g') 319 (0, 9) 320 >>> s = ' (abc(dd efy 442))xxg' 321 >>> startpos, endpos = Expression._scanBrackets(s) 322 >>> print(s[startpos+1:endpos]) 323 abc(dd efy 442) 324 """ 325 startpos = -1 326 endpos = -1 327 scandepth = 0 328 for scanpos in xrange(fromIndex, len(expr_str)): 329 scanchar = expr_str[scanpos] 330 if scanchar == "(": 331 if startpos == -1: 332 startpos = scanpos 333 scandepth += 1 334 elif scanchar == ")": 335 scandepth -= 1 336 if scandepth == 0: 337 endpos = scanpos 338 break 339 else: 340 if startpos != -1 or endpos != -1: 341 raise ValueError("expression syntax error (non-matching brackets?)") 342 return (startpos, endpos)
343 344 if __name__ == "__main__": 345 import doctest 346 doctest.testmod() 347