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