Mypal68/third_party/python/json-e/jsone/interpreter.py
2022-04-16 07:41:55 +03:00

290 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