forked from mirrors/gecko-dev
		
	 a94444c14e
			
		
	
	
		a94444c14e
		
	
	
	
	
		
			
			MozReview-Commit-ID: FlucI8bQq4h --HG-- extra : rebase_source : 4cff4a912e33487b9f4133dae200eef051af39fd
		
			
				
	
	
		
			289 lines
		
	
	
	
		
			8.3 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			289 lines
		
	
	
	
		
			8.3 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| from __future__ import absolute_import, print_function, unicode_literals
 | |
| 
 | |
| from .prattparser import PrattParser, infix, prefix
 | |
| from .shared import TemplateError, InterpreterError, string
 | |
| import operator
 | |
| import json
 | |
| 
 | |
| OPERATORS = {
 | |
|     '-': operator.sub,
 | |
|     '*': operator.mul,
 | |
|     '/': operator.truediv,
 | |
|     '**': operator.pow,
 | |
|     '==': operator.eq,
 | |
|     '!=': operator.ne,
 | |
|     '<=': operator.le,
 | |
|     '<': operator.lt,
 | |
|     '>': operator.gt,
 | |
|     '>=': operator.ge,
 | |
|     '&&': lambda a, b: bool(a and b),
 | |
|     '||': lambda a, b: bool(a or b),
 | |
| }
 | |
| 
 | |
| 
 | |
| def infixExpectationError(operator, expected):
 | |
|     return InterpreterError('infix: {} expects {} {} {}'.
 | |
|                             format(operator, expected, operator, expected))
 | |
| 
 | |
| 
 | |
| class ExpressionEvaluator(PrattParser):
 | |
| 
 | |
|     ignore = '\\s+'
 | |
|     patterns = {
 | |
|         'number': '[0-9]+(?:\\.[0-9]+)?',
 | |
|         'identifier': '[a-zA-Z_][a-zA-Z_0-9]*',
 | |
|         'string': '\'[^\']*\'|"[^"]*"',
 | |
|         # avoid matching these as prefixes of identifiers e.g., `insinutations`
 | |
|         'true': 'true(?![a-zA-Z_0-9])',
 | |
|         'false': 'false(?![a-zA-Z_0-9])',
 | |
|         'in': 'in(?![a-zA-Z_0-9])',
 | |
|         'null': 'null(?![a-zA-Z_0-9])',
 | |
|     }
 | |
|     tokens = [
 | |
|         '**', '+', '-', '*', '/', '[', ']', '.', '(', ')', '{', '}', ':', ',',
 | |
|         '>=', '<=', '<', '>', '==', '!=', '!', '&&', '||', 'true', 'false', 'in',
 | |
|         'null', 'number', 'identifier', 'string',
 | |
|     ]
 | |
|     precedence = [
 | |
|         ['||'],
 | |
|         ['&&'],
 | |
|         ['in'],
 | |
|         ['==', '!='],
 | |
|         ['>=', '<=', '<', '>'],
 | |
|         ['+', '-'],
 | |
|         ['*', '/'],
 | |
|         ['**-right-associative'],
 | |
|         ['**'],
 | |
|         ['[', '.'],
 | |
|         ['('],
 | |
|         ['unary'],
 | |
|     ]
 | |
| 
 | |
|     def __init__(self, context):
 | |
|         super(ExpressionEvaluator, self).__init__()
 | |
|         self.context = context
 | |
| 
 | |
|     def parse(self, expression):
 | |
|         if not isinstance(expression, string):
 | |
|             raise TemplateError('expression to be evaluated must be a string')
 | |
|         return super(ExpressionEvaluator, self).parse(expression)
 | |
| 
 | |
|     @prefix('number')
 | |
|     def number(self, token, pc):
 | |
|         v = token.value
 | |
|         return float(v) if '.' in v else int(v)
 | |
| 
 | |
|     @prefix("!")
 | |
|     def bang(self, token, pc):
 | |
|         return not pc.parse('unary')
 | |
| 
 | |
|     @prefix("-")
 | |
|     def uminus(self, token, pc):
 | |
|         v = pc.parse('unary')
 | |
|         if not isNumber(v):
 | |
|             raise InterpreterError('{} expects {}'.format('unary -', 'number'))
 | |
|         return -v
 | |
| 
 | |
|     @prefix("+")
 | |
|     def uplus(self, token, pc):
 | |
|         v = pc.parse('unary')
 | |
|         if not isNumber(v):
 | |
|             raise InterpreterError('{} expects {}'.format('unary +', 'number'))
 | |
|         return v
 | |
| 
 | |
|     @prefix("identifier")
 | |
|     def identifier(self, token, pc):
 | |
|         try:
 | |
|             return self.context[token.value]
 | |
|         except KeyError:
 | |
|             raise InterpreterError(
 | |
|                 'unknown context value {}'.format(token.value))
 | |
| 
 | |
|     @prefix("null")
 | |
|     def null(self, token, pc):
 | |
|         return None
 | |
| 
 | |
|     @prefix("[")
 | |
|     def array_bracket(self, token, pc):
 | |
|         return parseList(pc, ',', ']')
 | |
| 
 | |
|     @prefix("(")
 | |
|     def grouping_paren(self, token, pc):
 | |
|         rv = pc.parse()
 | |
|         pc.require(')')
 | |
|         return rv
 | |
| 
 | |
|     @prefix("{")
 | |
|     def object_brace(self, token, pc):
 | |
|         return parseObject(pc)
 | |
| 
 | |
|     @prefix("string")
 | |
|     def string(self, token, pc):
 | |
|         return parseString(token.value)
 | |
| 
 | |
|     @prefix("true")
 | |
|     def true(self, token, pc):
 | |
|         return True
 | |
| 
 | |
|     @prefix("false")
 | |
|     def false(self, token, ps):
 | |
|         return False
 | |
| 
 | |
|     @infix("+")
 | |
|     def plus(self, left, token, pc):
 | |
|         if not isinstance(left, (string, int, float)) or isinstance(left, bool):
 | |
|             raise infixExpectationError('+', 'number/string')
 | |
|         right = pc.parse(token.kind)
 | |
|         if not isinstance(right, (string, int, float)) or isinstance(right, bool):
 | |
|             raise infixExpectationError('+', 'number/string')
 | |
|         if type(right) != type(left) and \
 | |
|                 (isinstance(left, string) or isinstance(right, string)):
 | |
|             raise infixExpectationError('+', 'numbers/strings')
 | |
|         return left + right
 | |
| 
 | |
|     @infix('-', '*', '/', '**')
 | |
|     def arith(self, left, token, pc):
 | |
|         op = token.kind
 | |
|         if not isNumber(left):
 | |
|             raise infixExpectationError(op, 'number')
 | |
|         right = pc.parse({'**': '**-right-associative'}.get(op))
 | |
|         if not isNumber(right):
 | |
|             raise infixExpectationError(op, 'number')
 | |
|         return OPERATORS[op](left, right)
 | |
| 
 | |
|     @infix("[")
 | |
|     def index_slice(self, left, token, pc):
 | |
|         a = None
 | |
|         b = None
 | |
|         is_interval = False
 | |
|         if pc.attempt(':'):
 | |
|             a = 0
 | |
|             is_interval = True
 | |
|         else:
 | |
|             a = pc.parse()
 | |
|             if pc.attempt(':'):
 | |
|                 is_interval = True
 | |
| 
 | |
|         if is_interval and not pc.attempt(']'):
 | |
|             b = pc.parse()
 | |
|             pc.require(']')
 | |
| 
 | |
|         if not is_interval:
 | |
|             pc.require(']')
 | |
| 
 | |
|         return accessProperty(left, a, b, is_interval)
 | |
| 
 | |
|     @infix(".")
 | |
|     def property_dot(self, left, token, pc):
 | |
|         if not isinstance(left, dict):
 | |
|             raise infixExpectationError('.', 'object')
 | |
|         k = pc.require('identifier').value
 | |
|         try:
 | |
|             return left[k]
 | |
|         except KeyError:
 | |
|             raise TemplateError(
 | |
|                 '{} not found in {}'.format(k, json.dumps(left)))
 | |
| 
 | |
|     @infix("(")
 | |
|     def function_call(self, left, token, pc):
 | |
|         if not callable(left):
 | |
|             raise TemplateError('function call', 'callable')
 | |
|         args = parseList(pc, ',', ')')
 | |
|         return left(*args)
 | |
| 
 | |
|     @infix('==', '!=', '||', '&&')
 | |
|     def equality_and_logic(self, left, token, pc):
 | |
|         op = token.kind
 | |
|         right = pc.parse(op)
 | |
|         return OPERATORS[op](left, right)
 | |
| 
 | |
|     @infix('<=', '<', '>', '>=')
 | |
|     def inequality(self, left, token, pc):
 | |
|         op = token.kind
 | |
|         right = pc.parse(op)
 | |
|         if type(left) != type(right) or \
 | |
|                 not (isinstance(left, (int, float, string)) and not isinstance(left, bool)):
 | |
|             raise infixExpectationError(op, 'numbers/strings')
 | |
|         return OPERATORS[op](left, right)
 | |
| 
 | |
|     @infix("in")
 | |
|     def contains(self, left, token, pc):
 | |
|         right = pc.parse(token.kind)
 | |
|         if isinstance(right, dict):
 | |
|             if not isinstance(left, string):
 | |
|                 raise infixExpectationError('in-object', 'string on left side')
 | |
|         elif isinstance(right, string):
 | |
|             if not isinstance(left, string):
 | |
|                 raise infixExpectationError('in-string', 'string on left side')
 | |
|         elif not isinstance(right, list):
 | |
|             raise infixExpectationError(
 | |
|                 'in', 'Array, string, or object on right side')
 | |
|         try:
 | |
|             return left in right
 | |
|         except TypeError:
 | |
|             raise infixExpectationError('in', 'scalar value, collection')
 | |
| 
 | |
| 
 | |
| def isNumber(v):
 | |
|     return isinstance(v, (int, float)) and not isinstance(v, bool)
 | |
| 
 | |
| 
 | |
| def parseString(v):
 | |
|     return v[1:-1]
 | |
| 
 | |
| 
 | |
| def parseList(pc, separator, terminator):
 | |
|     rv = []
 | |
|     if not pc.attempt(terminator):
 | |
|         while True:
 | |
|             rv.append(pc.parse())
 | |
|             if not pc.attempt(separator):
 | |
|                 break
 | |
|         pc.require(terminator)
 | |
|     return rv
 | |
| 
 | |
| 
 | |
| def parseObject(pc):
 | |
|     rv = {}
 | |
|     if not pc.attempt('}'):
 | |
|         while True:
 | |
|             k = pc.require('identifier', 'string')
 | |
|             if k.kind == 'string':
 | |
|                 k = parseString(k.value)
 | |
|             else:
 | |
|                 k = k.value
 | |
|             pc.require(':')
 | |
|             v = pc.parse()
 | |
|             rv[k] = v
 | |
|             if not pc.attempt(','):
 | |
|                 break
 | |
|         pc.require('}')
 | |
|     return rv
 | |
| 
 | |
| 
 | |
| def accessProperty(value, a, b, is_interval):
 | |
|     if isinstance(value, (list, string)):
 | |
|         if is_interval:
 | |
|             if b is None:
 | |
|                 b = len(value)
 | |
|             try:
 | |
|                 return value[a:b]
 | |
|             except TypeError:
 | |
|                 raise infixExpectationError('[..]', 'integer')
 | |
|         else:
 | |
|             try:
 | |
|                 return value[a]
 | |
|             except IndexError:
 | |
|                 raise TemplateError('index out of bounds')
 | |
|             except TypeError:
 | |
|                 raise infixExpectationError('[..]', 'integer')
 | |
| 
 | |
|     if not isinstance(value, dict):
 | |
|         raise infixExpectationError('[..]', 'object, array, or string')
 | |
|     if not isinstance(a, string):
 | |
|         raise infixExpectationError('[..]', 'string index')
 | |
| 
 | |
|     try:
 | |
|         return value[a]
 | |
|     except KeyError:
 | |
|         return None
 |