1from __future__ import absolute_import, print_function, unicode_literals 2 3from .prattparser import PrattParser, infix, prefix 4from .shared import TemplateError, InterpreterError, string 5import operator 6import json 7 8OPERATORS = { 9 '-': operator.sub, 10 '*': operator.mul, 11 '/': operator.truediv, 12 '**': operator.pow, 13 '==': operator.eq, 14 '!=': operator.ne, 15 '<=': operator.le, 16 '<': operator.lt, 17 '>': operator.gt, 18 '>=': operator.ge, 19 '&&': lambda a, b: bool(a and b), 20 '||': lambda a, b: bool(a or b), 21} 22 23 24def infixExpectationError(operator, expected): 25 return InterpreterError('infix: {} expects {} {} {}'. 26 format(operator, expected, operator, expected)) 27 28 29class ExpressionEvaluator(PrattParser): 30 31 ignore = '\\s+' 32 patterns = { 33 'number': '[0-9]+(?:\\.[0-9]+)?', 34 'identifier': '[a-zA-Z_][a-zA-Z_0-9]*', 35 'string': '\'[^\']*\'|"[^"]*"', 36 # avoid matching these as prefixes of identifiers e.g., `insinutations` 37 'true': 'true(?![a-zA-Z_0-9])', 38 'false': 'false(?![a-zA-Z_0-9])', 39 'in': 'in(?![a-zA-Z_0-9])', 40 'null': 'null(?![a-zA-Z_0-9])', 41 } 42 tokens = [ 43 '**', '+', '-', '*', '/', '[', ']', '.', '(', ')', '{', '}', ':', ',', 44 '>=', '<=', '<', '>', '==', '!=', '!', '&&', '||', 'true', 'false', 'in', 45 'null', 'number', 'identifier', 'string', 46 ] 47 precedence = [ 48 ['||'], 49 ['&&'], 50 ['in'], 51 ['==', '!='], 52 ['>=', '<=', '<', '>'], 53 ['+', '-'], 54 ['*', '/'], 55 ['**-right-associative'], 56 ['**'], 57 ['[', '.'], 58 ['('], 59 ['unary'], 60 ] 61 62 def __init__(self, context): 63 super(ExpressionEvaluator, self).__init__() 64 self.context = context 65 66 def parse(self, expression): 67 if not isinstance(expression, string): 68 raise TemplateError('expression to be evaluated must be a string') 69 return super(ExpressionEvaluator, self).parse(expression) 70 71 @prefix('number') 72 def number(self, token, pc): 73 v = token.value 74 return float(v) if '.' in v else int(v) 75 76 @prefix("!") 77 def bang(self, token, pc): 78 return not pc.parse('unary') 79 80 @prefix("-") 81 def uminus(self, token, pc): 82 v = pc.parse('unary') 83 if not isNumber(v): 84 raise InterpreterError('{} expects {}'.format('unary -', 'number')) 85 return -v 86 87 @prefix("+") 88 def uplus(self, token, pc): 89 v = pc.parse('unary') 90 if not isNumber(v): 91 raise InterpreterError('{} expects {}'.format('unary +', 'number')) 92 return v 93 94 @prefix("identifier") 95 def identifier(self, token, pc): 96 try: 97 return self.context[token.value] 98 except KeyError: 99 raise InterpreterError( 100 'unknown context value {}'.format(token.value)) 101 102 @prefix("null") 103 def null(self, token, pc): 104 return None 105 106 @prefix("[") 107 def array_bracket(self, token, pc): 108 return parseList(pc, ',', ']') 109 110 @prefix("(") 111 def grouping_paren(self, token, pc): 112 rv = pc.parse() 113 pc.require(')') 114 return rv 115 116 @prefix("{") 117 def object_brace(self, token, pc): 118 return parseObject(pc) 119 120 @prefix("string") 121 def string(self, token, pc): 122 return parseString(token.value) 123 124 @prefix("true") 125 def true(self, token, pc): 126 return True 127 128 @prefix("false") 129 def false(self, token, ps): 130 return False 131 132 @infix("+") 133 def plus(self, left, token, pc): 134 if not isinstance(left, (string, int, float)) or isinstance(left, bool): 135 raise infixExpectationError('+', 'number/string') 136 right = pc.parse(token.kind) 137 if not isinstance(right, (string, int, float)) or isinstance(right, bool): 138 raise infixExpectationError('+', 'number/string') 139 if type(right) != type(left) and \ 140 (isinstance(left, string) or isinstance(right, string)): 141 raise infixExpectationError('+', 'numbers/strings') 142 return left + right 143 144 @infix('-', '*', '/', '**') 145 def arith(self, left, token, pc): 146 op = token.kind 147 if not isNumber(left): 148 raise infixExpectationError(op, 'number') 149 right = pc.parse({'**': '**-right-associative'}.get(op)) 150 if not isNumber(right): 151 raise infixExpectationError(op, 'number') 152 return OPERATORS[op](left, right) 153 154 @infix("[") 155 def index_slice(self, left, token, pc): 156 a = None 157 b = None 158 is_interval = False 159 if pc.attempt(':'): 160 a = 0 161 is_interval = True 162 else: 163 a = pc.parse() 164 if pc.attempt(':'): 165 is_interval = True 166 167 if is_interval and not pc.attempt(']'): 168 b = pc.parse() 169 pc.require(']') 170 171 if not is_interval: 172 pc.require(']') 173 174 return accessProperty(left, a, b, is_interval) 175 176 @infix(".") 177 def property_dot(self, left, token, pc): 178 if not isinstance(left, dict): 179 raise infixExpectationError('.', 'object') 180 k = pc.require('identifier').value 181 try: 182 return left[k] 183 except KeyError: 184 raise TemplateError( 185 '{} not found in {}'.format(k, json.dumps(left))) 186 187 @infix("(") 188 def function_call(self, left, token, pc): 189 if not callable(left): 190 raise TemplateError('function call', 'callable') 191 args = parseList(pc, ',', ')') 192 return left(*args) 193 194 @infix('==', '!=', '||', '&&') 195 def equality_and_logic(self, left, token, pc): 196 op = token.kind 197 right = pc.parse(op) 198 return OPERATORS[op](left, right) 199 200 @infix('<=', '<', '>', '>=') 201 def inequality(self, left, token, pc): 202 op = token.kind 203 right = pc.parse(op) 204 if type(left) != type(right) or \ 205 not (isinstance(left, (int, float, string)) and not isinstance(left, bool)): 206 raise infixExpectationError(op, 'numbers/strings') 207 return OPERATORS[op](left, right) 208 209 @infix("in") 210 def contains(self, left, token, pc): 211 right = pc.parse(token.kind) 212 if isinstance(right, dict): 213 if not isinstance(left, string): 214 raise infixExpectationError('in-object', 'string on left side') 215 elif isinstance(right, string): 216 if not isinstance(left, string): 217 raise infixExpectationError('in-string', 'string on left side') 218 elif not isinstance(right, list): 219 raise infixExpectationError( 220 'in', 'Array, string, or object on right side') 221 try: 222 return left in right 223 except TypeError: 224 raise infixExpectationError('in', 'scalar value, collection') 225 226 227def isNumber(v): 228 return isinstance(v, (int, float)) and not isinstance(v, bool) 229 230 231def parseString(v): 232 return v[1:-1] 233 234 235def parseList(pc, separator, terminator): 236 rv = [] 237 if not pc.attempt(terminator): 238 while True: 239 rv.append(pc.parse()) 240 if not pc.attempt(separator): 241 break 242 pc.require(terminator) 243 return rv 244 245 246def parseObject(pc): 247 rv = {} 248 if not pc.attempt('}'): 249 while True: 250 k = pc.require('identifier', 'string') 251 if k.kind == 'string': 252 k = parseString(k.value) 253 else: 254 k = k.value 255 pc.require(':') 256 v = pc.parse() 257 rv[k] = v 258 if not pc.attempt(','): 259 break 260 pc.require('}') 261 return rv 262 263 264def accessProperty(value, a, b, is_interval): 265 if isinstance(value, (list, string)): 266 if is_interval: 267 if b is None: 268 b = len(value) 269 try: 270 return value[a:b] 271 except TypeError: 272 raise infixExpectationError('[..]', 'integer') 273 else: 274 try: 275 return value[a] 276 except IndexError: 277 raise TemplateError('index out of bounds') 278 except TypeError: 279 raise infixExpectationError('[..]', 'integer') 280 281 if not isinstance(value, dict): 282 raise infixExpectationError('[..]', 'object, array, or string') 283 if not isinstance(a, string): 284 raise infixExpectationError('[..]', 'string index') 285 286 try: 287 return value[a] 288 except KeyError: 289 return None 290