diff options
Diffstat (limited to 'third_party/python/json-e/jsone/interpreter.py')
-rw-r--r-- | third_party/python/json-e/jsone/interpreter.py | 289 |
1 files changed, 289 insertions, 0 deletions
diff --git a/third_party/python/json-e/jsone/interpreter.py b/third_party/python/json-e/jsone/interpreter.py new file mode 100644 index 0000000000..eb38a9c85b --- /dev/null +++ b/third_party/python/json-e/jsone/interpreter.py @@ -0,0 +1,289 @@ +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 |