expression-py 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- expression/__init__.py +350 -0
- expression/grammar.peg +191 -0
- expression/helpers.py +169 -0
- expression/parser.py +968 -0
- expression/tokenizer.py +171 -0
- expression_py-0.1.0.dist-info/METADATA +350 -0
- expression_py-0.1.0.dist-info/RECORD +10 -0
- expression_py-0.1.0.dist-info/WHEEL +5 -0
- expression_py-0.1.0.dist-info/licenses/LICENSE +674 -0
- expression_py-0.1.0.dist-info/top_level.txt +1 -0
expression/__init__.py
ADDED
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
# Copyright (C) 2026 Jakub T. Jankiewicz <https://jcu.bi/>
|
|
2
|
+
#
|
|
3
|
+
# This file is part of expression.py.
|
|
4
|
+
#
|
|
5
|
+
# expression.py is free software: you can redistribute it and/or modify
|
|
6
|
+
# it under the terms of the GNU General Public License as published by
|
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
# (at your option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# expression.py is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU General Public License for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU General Public License
|
|
16
|
+
# along with expression.py. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
import math
|
|
19
|
+
|
|
20
|
+
from expression.parser import ExpressionParser as _GeneratedParser
|
|
21
|
+
from expression.tokenizer import ExpressionTokenizer
|
|
22
|
+
from expression.helpers import (
|
|
23
|
+
with_type, is_typed, is_string_type, is_array_type,
|
|
24
|
+
validate_number, validate_types, maybe_regex, to_array,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ExpressionParserExt(_GeneratedParser):
|
|
29
|
+
KEYWORDS = ('true', 'false', 'null', 'in')
|
|
30
|
+
SOFT_KEYWORDS = ()
|
|
31
|
+
|
|
32
|
+
def __init__(self, tokenizer, variables, constants, functions, source_text):
|
|
33
|
+
super().__init__(tokenizer, verbose=False)
|
|
34
|
+
self.variables = variables
|
|
35
|
+
self.constants = constants
|
|
36
|
+
self.functions = functions
|
|
37
|
+
self._source_text = source_text
|
|
38
|
+
|
|
39
|
+
def regex_literal(self):
|
|
40
|
+
tok = self._tokenizer.peek()
|
|
41
|
+
if tok.type == ExpressionTokenizer.REGEX_TOKEN_TYPE:
|
|
42
|
+
return self._tokenizer.getnext()
|
|
43
|
+
return None
|
|
44
|
+
|
|
45
|
+
def function_assignment(self):
|
|
46
|
+
mark = self._mark()
|
|
47
|
+
name_tok = self.name()
|
|
48
|
+
if name_tok is None:
|
|
49
|
+
self._reset(mark)
|
|
50
|
+
return None
|
|
51
|
+
if not self.expect('('):
|
|
52
|
+
self._reset(mark)
|
|
53
|
+
return None
|
|
54
|
+
params = []
|
|
55
|
+
rparen = self.expect(')')
|
|
56
|
+
if not rparen:
|
|
57
|
+
p = self.name()
|
|
58
|
+
if p is None:
|
|
59
|
+
self._reset(mark)
|
|
60
|
+
return None
|
|
61
|
+
params.append(p.string)
|
|
62
|
+
while self.expect(','):
|
|
63
|
+
p = self.name()
|
|
64
|
+
if p is None:
|
|
65
|
+
self._reset(mark)
|
|
66
|
+
return None
|
|
67
|
+
params.append(p.string)
|
|
68
|
+
rparen = self.expect(')')
|
|
69
|
+
if not rparen:
|
|
70
|
+
self._reset(mark)
|
|
71
|
+
return None
|
|
72
|
+
eq = self.expect('=')
|
|
73
|
+
if not eq:
|
|
74
|
+
self._reset(mark)
|
|
75
|
+
return None
|
|
76
|
+
next_tok = self._tokenizer.peek()
|
|
77
|
+
if next_tok.string in ('=', '~'):
|
|
78
|
+
self._reset(mark)
|
|
79
|
+
return None
|
|
80
|
+
body_start = next_tok.start[1]
|
|
81
|
+
body = self._source_text[body_start:].rstrip(';').strip()
|
|
82
|
+
while self._tokenizer.peek().string != '' and self._tokenizer.peek().type != 0:
|
|
83
|
+
self._tokenizer.getnext()
|
|
84
|
+
return self._func_assign(name_tok.string, params, body)
|
|
85
|
+
|
|
86
|
+
def _func_assign(self, name, params, body=None):
|
|
87
|
+
if body is None:
|
|
88
|
+
body_start = self._tokenizer.peek().start[1]
|
|
89
|
+
body = self._source_text[body_start:].rstrip(';').strip()
|
|
90
|
+
|
|
91
|
+
captured_params = list(params)
|
|
92
|
+
captured_body = body
|
|
93
|
+
|
|
94
|
+
def func(*args):
|
|
95
|
+
expr = Expression()
|
|
96
|
+
for i, p in enumerate(captured_params):
|
|
97
|
+
expr.variables[p] = args[i]
|
|
98
|
+
return expr.evaluate(captured_body)
|
|
99
|
+
|
|
100
|
+
self.functions[name] = func
|
|
101
|
+
return with_type(True)
|
|
102
|
+
|
|
103
|
+
def _var_assign(self, name, value):
|
|
104
|
+
if name in self.constants:
|
|
105
|
+
raise Exception(f"Can't assign value to constant '{name}'")
|
|
106
|
+
self.variables[name] = value
|
|
107
|
+
return value
|
|
108
|
+
|
|
109
|
+
def _resolve_var(self, name):
|
|
110
|
+
if name in ('true', 'false', 'null'):
|
|
111
|
+
return None
|
|
112
|
+
if name in self.constants:
|
|
113
|
+
return with_type(self.constants[name])
|
|
114
|
+
if name in self.variables:
|
|
115
|
+
return with_type(self.variables[name])
|
|
116
|
+
raise Exception(f"Variable '{name}' not found")
|
|
117
|
+
|
|
118
|
+
def _parse_string(self, tok):
|
|
119
|
+
raw = tok.string
|
|
120
|
+
if raw.startswith('"'):
|
|
121
|
+
value = raw[1:-1].replace('\\"', '"').replace('\\\\', '\\')
|
|
122
|
+
else:
|
|
123
|
+
value = raw[1:-1].replace("\\'", "'").replace('\\\\', '\\')
|
|
124
|
+
return maybe_regex(value)
|
|
125
|
+
|
|
126
|
+
def _parse_number(self, tok):
|
|
127
|
+
s = tok.string
|
|
128
|
+
if s.startswith('0x') or s.startswith('0X'):
|
|
129
|
+
return with_type(int(s, 16))
|
|
130
|
+
if s.startswith('0b') or s.startswith('0B'):
|
|
131
|
+
return with_type(int(s, 2))
|
|
132
|
+
if '.' in s or 'e' in s.lower():
|
|
133
|
+
return with_type(float(s))
|
|
134
|
+
return with_type(int(s))
|
|
135
|
+
|
|
136
|
+
def _compare_op(self, op, a, b, fn):
|
|
137
|
+
validate_types(['integer', 'double', 'boolean'], op, a)
|
|
138
|
+
validate_types(['integer', 'double', 'boolean'], op, b)
|
|
139
|
+
return with_type(fn(a['value'], b['value']))
|
|
140
|
+
|
|
141
|
+
def _shift_op(self, op, a, b, fn):
|
|
142
|
+
validate_number(op, a)
|
|
143
|
+
validate_number(op, b)
|
|
144
|
+
return with_type(fn(int(a['value']), int(b['value'])))
|
|
145
|
+
|
|
146
|
+
def _plus(self, a, b):
|
|
147
|
+
if is_array_type(a) or is_array_type(b):
|
|
148
|
+
return with_type(to_array(a) + to_array(b), 'array')
|
|
149
|
+
if is_string_type(b):
|
|
150
|
+
return with_type(str(a['value']) + b['value'])
|
|
151
|
+
validate_number('+', b)
|
|
152
|
+
validate_number('+', a)
|
|
153
|
+
return with_type(a['value'] + b['value'])
|
|
154
|
+
|
|
155
|
+
def _minus(self, a, b):
|
|
156
|
+
if is_array_type(a) or is_array_type(b):
|
|
157
|
+
right = to_array(b)
|
|
158
|
+
return with_type([x for x in to_array(a) if x not in right], 'array')
|
|
159
|
+
validate_number('-', b)
|
|
160
|
+
validate_number('-', a)
|
|
161
|
+
return with_type(a['value'] - b['value'])
|
|
162
|
+
|
|
163
|
+
def _mul(self, a, b):
|
|
164
|
+
if is_array_type(a) or is_array_type(b):
|
|
165
|
+
if is_array_type(a) and is_string_type(b):
|
|
166
|
+
return with_type(b['value'].join(str(x) for x in a['value']))
|
|
167
|
+
if is_array_type(b) and is_string_type(a):
|
|
168
|
+
return with_type(a['value'].join(str(x) for x in b['value']))
|
|
169
|
+
arr = a if is_array_type(a) else b
|
|
170
|
+
num = b if is_array_type(a) else a
|
|
171
|
+
validate_number('*', num)
|
|
172
|
+
return with_type(list(arr['value']) * int(num['value']), 'array')
|
|
173
|
+
if is_string_type(a) or is_string_type(b):
|
|
174
|
+
s = a if is_string_type(a) else b
|
|
175
|
+
num = b if is_string_type(a) else a
|
|
176
|
+
validate_number('*', num)
|
|
177
|
+
return with_type(s['value'] * int(num['value']))
|
|
178
|
+
validate_number('*', a)
|
|
179
|
+
validate_number('*', b)
|
|
180
|
+
return with_type(a['value'] * b['value'])
|
|
181
|
+
|
|
182
|
+
def _union(self, a, b):
|
|
183
|
+
if not is_array_type(a) and not is_array_type(b):
|
|
184
|
+
validate_number('|', a)
|
|
185
|
+
validate_number('|', b)
|
|
186
|
+
return with_type(int(a['value']) | int(b['value']))
|
|
187
|
+
result = []
|
|
188
|
+
for x in to_array(a) + to_array(b):
|
|
189
|
+
if x not in result:
|
|
190
|
+
result.append(x)
|
|
191
|
+
return with_type(result, 'array')
|
|
192
|
+
|
|
193
|
+
def _intersect(self, a, b):
|
|
194
|
+
if not is_array_type(a) and not is_array_type(b):
|
|
195
|
+
validate_number('&', a)
|
|
196
|
+
validate_number('&', b)
|
|
197
|
+
return with_type(int(a['value']) & int(b['value']))
|
|
198
|
+
right = to_array(b)
|
|
199
|
+
result = []
|
|
200
|
+
for x in to_array(a):
|
|
201
|
+
if x in right and x not in result:
|
|
202
|
+
result.append(x)
|
|
203
|
+
return with_type(result, 'array')
|
|
204
|
+
|
|
205
|
+
def _lshift(self, a, b):
|
|
206
|
+
if is_array_type(a):
|
|
207
|
+
a['value'].append(b['value'])
|
|
208
|
+
return a
|
|
209
|
+
if is_string_type(a):
|
|
210
|
+
a['value'] = a['value'] + str(b['value'])
|
|
211
|
+
return a
|
|
212
|
+
return self._shift_op('<<', a, b, lambda x, y: x << y)
|
|
213
|
+
|
|
214
|
+
def _in(self, a, b):
|
|
215
|
+
if is_array_type(b):
|
|
216
|
+
return with_type(a['value'] in b['value'])
|
|
217
|
+
if is_string_type(b):
|
|
218
|
+
return with_type(str(a['value']) in b['value'])
|
|
219
|
+
return with_type(a['value'] in [b['value']])
|
|
220
|
+
|
|
221
|
+
def _spaceship(self, a, b):
|
|
222
|
+
x = a['value']
|
|
223
|
+
y = b['value']
|
|
224
|
+
if x < y:
|
|
225
|
+
return with_type(-1)
|
|
226
|
+
if x > y:
|
|
227
|
+
return with_type(1)
|
|
228
|
+
return with_type(0)
|
|
229
|
+
|
|
230
|
+
def _div(self, a, b):
|
|
231
|
+
validate_number('/', a)
|
|
232
|
+
validate_number('/', b)
|
|
233
|
+
if b['value'] == 0:
|
|
234
|
+
raise ZeroDivisionError("Division by zero")
|
|
235
|
+
return with_type(a['value'] / b['value'])
|
|
236
|
+
|
|
237
|
+
def _mod(self, a, b):
|
|
238
|
+
validate_number('%', a)
|
|
239
|
+
validate_number('%', b)
|
|
240
|
+
return with_type(a['value'] % b['value'])
|
|
241
|
+
|
|
242
|
+
def _property_access(self, obj, prop):
|
|
243
|
+
validate_types(['array'], '[', obj)
|
|
244
|
+
validate_types(['string', 'double', 'integer', 'boolean'], '[', prop)
|
|
245
|
+
return with_type(obj['value'][prop['value']])
|
|
246
|
+
|
|
247
|
+
def _implicit_mul(self, a, b):
|
|
248
|
+
validate_number('[*]', a)
|
|
249
|
+
validate_number('[*]', b)
|
|
250
|
+
return with_type(a['value'] * b['value'])
|
|
251
|
+
|
|
252
|
+
def _unary_minus(self, a):
|
|
253
|
+
validate_number('-', a)
|
|
254
|
+
return with_type(a['value'] * -1)
|
|
255
|
+
|
|
256
|
+
def _unary_plus(self, a):
|
|
257
|
+
if is_string_type(a):
|
|
258
|
+
return with_type(float(a['value']))
|
|
259
|
+
return a
|
|
260
|
+
|
|
261
|
+
def _call_function(self, name, args):
|
|
262
|
+
BUILTIN_MAP = {
|
|
263
|
+
'arcsin': 'asin', 'arcsinh': 'asinh',
|
|
264
|
+
'arccos': 'acos', 'arccosh': 'acosh',
|
|
265
|
+
'arctan': 'atan', 'arctanh': 'atanh',
|
|
266
|
+
'ln': 'log',
|
|
267
|
+
}
|
|
268
|
+
BUILTIN_FUNCTIONS = [
|
|
269
|
+
'sin', 'sinh', 'asin', 'asinh',
|
|
270
|
+
'cos', 'cosh', 'acos', 'acosh',
|
|
271
|
+
'tan', 'tanh', 'atan', 'atanh',
|
|
272
|
+
'sqrt', 'abs', 'ln', 'log',
|
|
273
|
+
]
|
|
274
|
+
resolved = BUILTIN_MAP.get(name, name)
|
|
275
|
+
is_builtin = resolved in BUILTIN_FUNCTIONS
|
|
276
|
+
is_custom = name in self.functions
|
|
277
|
+
if not is_builtin and not is_custom:
|
|
278
|
+
raise Exception(f"function '{name}' doesn't exist")
|
|
279
|
+
arg_values = [a['value'] for a in args]
|
|
280
|
+
if is_builtin:
|
|
281
|
+
if resolved == 'abs':
|
|
282
|
+
func = abs
|
|
283
|
+
else:
|
|
284
|
+
func = getattr(math, resolved)
|
|
285
|
+
result = func(*arg_values)
|
|
286
|
+
else:
|
|
287
|
+
result = self.functions[name](*arg_values)
|
|
288
|
+
return with_type(result)
|
|
289
|
+
|
|
290
|
+
def _build_json_obj(self, key_tok, value, rest):
|
|
291
|
+
key = self._json_string_val(key_tok)
|
|
292
|
+
d = {key: value}
|
|
293
|
+
d.update(rest)
|
|
294
|
+
return d
|
|
295
|
+
|
|
296
|
+
def _make_obj(self, key_tok, value):
|
|
297
|
+
key = self._json_string_val(key_tok)
|
|
298
|
+
return {key: value}
|
|
299
|
+
|
|
300
|
+
def _json_string_val(self, tok):
|
|
301
|
+
raw = tok.string
|
|
302
|
+
if raw.startswith('"'):
|
|
303
|
+
return raw[1:-1].replace('\\"', '"').replace('\\\\', '\\')
|
|
304
|
+
return raw[1:-1].replace("\\'", "'").replace('\\\\', '\\')
|
|
305
|
+
|
|
306
|
+
def _json_number_val(self, tok):
|
|
307
|
+
s = tok.string
|
|
308
|
+
if '.' in s or 'e' in s.lower():
|
|
309
|
+
return float(s)
|
|
310
|
+
return int(s)
|
|
311
|
+
|
|
312
|
+
def _merge_obj(self, key_tok, value, rest):
|
|
313
|
+
key = self._json_string_val(key_tok)
|
|
314
|
+
d = {key: value}
|
|
315
|
+
d.update(rest)
|
|
316
|
+
return d
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
class Expression:
|
|
320
|
+
def __init__(self):
|
|
321
|
+
self.constants = {"e": math.e, "pi": math.pi}
|
|
322
|
+
self.variables = {}
|
|
323
|
+
self.functions = {}
|
|
324
|
+
self.suppress_errors = False
|
|
325
|
+
self.last_error = ""
|
|
326
|
+
|
|
327
|
+
def evaluate(self, expr):
|
|
328
|
+
if not expr or not expr.strip():
|
|
329
|
+
return None
|
|
330
|
+
text = expr.strip()
|
|
331
|
+
tokenizer = ExpressionTokenizer(text)
|
|
332
|
+
parser = ExpressionParserExt(
|
|
333
|
+
tokenizer, self.variables, self.constants, self.functions, text
|
|
334
|
+
)
|
|
335
|
+
try:
|
|
336
|
+
result = parser.start()
|
|
337
|
+
if result is None:
|
|
338
|
+
raise Exception(f"invalid syntax: {expr}")
|
|
339
|
+
self.variables = parser.variables
|
|
340
|
+
self.functions = parser.functions
|
|
341
|
+
if is_typed(result):
|
|
342
|
+
return result['value']
|
|
343
|
+
return result
|
|
344
|
+
except ZeroDivisionError:
|
|
345
|
+
raise
|
|
346
|
+
except Exception as e:
|
|
347
|
+
self.last_error = str(e) + f" in expression: {expr}"
|
|
348
|
+
if not self.suppress_errors:
|
|
349
|
+
raise
|
|
350
|
+
return None
|
expression/grammar.peg
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# Copyright (C) 2026 Jakub T. Jankiewicz <https://jcu.bi/>
|
|
2
|
+
#
|
|
3
|
+
# This file is part of expression.py.
|
|
4
|
+
#
|
|
5
|
+
# expression.py is free software: you can redistribute it and/or modify
|
|
6
|
+
# it under the terms of the GNU General Public License as published by
|
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
# (at your option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# expression.py is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU General Public License for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU General Public License
|
|
16
|
+
# along with expression.py. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
#
|
|
18
|
+
# PEG grammar for the expression evaluation language, in pegen format.
|
|
19
|
+
|
|
20
|
+
@class ExpressionParser
|
|
21
|
+
@subheader """\
|
|
22
|
+
from expression.helpers import (
|
|
23
|
+
with_type, is_typed, is_number, is_string_type, is_array_type,
|
|
24
|
+
validate_number, validate_types, maybe_regex, do_check_equal,
|
|
25
|
+
do_match, do_power, loose_equal, to_array,
|
|
26
|
+
)
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
start[object]: a=start_rule ENDMARKER { a }
|
|
30
|
+
|
|
31
|
+
start_rule[object]:
|
|
32
|
+
| function_assignment
|
|
33
|
+
| variable_assignment
|
|
34
|
+
| a=expr ';' { a }
|
|
35
|
+
| expr
|
|
36
|
+
|
|
37
|
+
function_assignment[object]:
|
|
38
|
+
| name=NAME '(' params=param_list_inner ')' '=' func_rest { self._func_assign(name.string, params) }
|
|
39
|
+
| name=NAME '(' ')' '=' func_rest { self._func_assign(name.string, []) }
|
|
40
|
+
|
|
41
|
+
param_list_inner[list]:
|
|
42
|
+
| a=NAME ',' b=param_list_inner { [a.string] + b }
|
|
43
|
+
| a=NAME { [a.string] }
|
|
44
|
+
|
|
45
|
+
func_rest[object]:
|
|
46
|
+
| a=expr ';' { a }
|
|
47
|
+
| a=expr { a }
|
|
48
|
+
|
|
49
|
+
variable_assignment[object]:
|
|
50
|
+
| name=NAME '=' !'=' !'~' value=expr ';' { self._var_assign(name.string, value) }
|
|
51
|
+
| name=NAME '=' !'=' !'~' value=expr { self._var_assign(name.string, value) }
|
|
52
|
+
|
|
53
|
+
expr[object]: ternary
|
|
54
|
+
|
|
55
|
+
ternary[object]:
|
|
56
|
+
| a=boolean '?' b=expr ':' c=ternary { b if a['value'] else c }
|
|
57
|
+
| boolean
|
|
58
|
+
|
|
59
|
+
boolean[object]:
|
|
60
|
+
| a=boolean '&&' b=compare { with_type(b['value'] if a['value'] else a['value']) }
|
|
61
|
+
| a=boolean '||' b=compare { with_type(a['value'] if a['value'] else b['value']) }
|
|
62
|
+
| compare
|
|
63
|
+
|
|
64
|
+
compare[object]:
|
|
65
|
+
| a=compare '===' b=bitshift { with_type(type(a['value']) == type(b['value']) and a['value'] == b['value']) }
|
|
66
|
+
| a=compare '!==' b=bitshift { with_type(not (type(a['value']) == type(b['value']) and a['value'] == b['value'])) }
|
|
67
|
+
| a=compare '<=>' b=bitshift { self._spaceship(a, b) }
|
|
68
|
+
| a=compare '==' b=bitshift { do_check_equal(a, b, lambda x, y: loose_equal(x, y)) }
|
|
69
|
+
| a=compare '=~' b=bitshift { do_match(a, b, self.variables) }
|
|
70
|
+
| a=compare '!=' b=bitshift { do_check_equal(a, b, lambda x, y: not loose_equal(x, y)) }
|
|
71
|
+
| a=compare '>=' b=bitshift { self._compare_op('>=', a, b, lambda x, y: x >= y) }
|
|
72
|
+
| a=compare '<=' b=bitshift { self._compare_op('<=', a, b, lambda x, y: x <= y) }
|
|
73
|
+
| a=compare '>' b=bitshift { self._compare_op('>', a, b, lambda x, y: x > y) }
|
|
74
|
+
| a=compare '<' b=bitshift { self._compare_op('<', a, b, lambda x, y: x < y) }
|
|
75
|
+
| a=compare 'in' b=bitshift { self._in(a, b) }
|
|
76
|
+
| bitshift
|
|
77
|
+
|
|
78
|
+
bitshift[object]:
|
|
79
|
+
| a=bitshift '<<' b=sum_expr { self._lshift(a, b) }
|
|
80
|
+
| a=bitshift '>>' b=sum_expr { self._shift_op('>>', a, b, lambda x, y: x >> y) }
|
|
81
|
+
| sum_expr
|
|
82
|
+
|
|
83
|
+
sum_expr[object]:
|
|
84
|
+
| a=sum_expr '+' b=product { self._plus(a, b) }
|
|
85
|
+
| a=sum_expr '-' b=product { self._minus(a, b) }
|
|
86
|
+
| a=sum_expr '|' b=product { self._union(a, b) }
|
|
87
|
+
| product
|
|
88
|
+
|
|
89
|
+
product[object]:
|
|
90
|
+
| a=product '*' !'*' b=unary { self._mul(a, b) }
|
|
91
|
+
| a=product '/' b=unary { self._div(a, b) }
|
|
92
|
+
| a=product '%' b=unary { self._mod(a, b) }
|
|
93
|
+
| a=product '&' b=unary { self._intersect(a, b) }
|
|
94
|
+
| a=product '[' b=expr ']' { self._property_access(a, b) }
|
|
95
|
+
| a=product b=implicit_mul { self._implicit_mul(a, b) }
|
|
96
|
+
| unary
|
|
97
|
+
|
|
98
|
+
implicit_mul[object]:
|
|
99
|
+
| paren_expr_pow
|
|
100
|
+
| func_call_pow
|
|
101
|
+
| var_ref_pow
|
|
102
|
+
|
|
103
|
+
paren_expr_pow[object]:
|
|
104
|
+
| a=paren_expr '**' b=value { do_power(a, b) }
|
|
105
|
+
| a=paren_expr '^' b=value { do_power(a, b) }
|
|
106
|
+
| paren_expr
|
|
107
|
+
|
|
108
|
+
func_call_pow[object]:
|
|
109
|
+
| a=function_call '**' b=value { do_power(a, b) }
|
|
110
|
+
| a=function_call '^' b=value { do_power(a, b) }
|
|
111
|
+
| function_call
|
|
112
|
+
|
|
113
|
+
var_ref_pow[object]:
|
|
114
|
+
| a=var_ref '**' b=value { do_power(a, b) }
|
|
115
|
+
| a=var_ref '^' b=value { do_power(a, b) }
|
|
116
|
+
| var_ref
|
|
117
|
+
|
|
118
|
+
unary[object]:
|
|
119
|
+
| '!' a=unary { with_type(not a['value']) }
|
|
120
|
+
| '-' a=unary { self._unary_minus(a) }
|
|
121
|
+
| '+' a=unary { self._unary_plus(a) }
|
|
122
|
+
| power
|
|
123
|
+
|
|
124
|
+
power[object]:
|
|
125
|
+
| a=value '**' b=power { do_power(a, b) }
|
|
126
|
+
| a=value '^' b=power { do_power(a, b) }
|
|
127
|
+
| value
|
|
128
|
+
|
|
129
|
+
value[object]:
|
|
130
|
+
| json_value
|
|
131
|
+
| const_value
|
|
132
|
+
| regex_value
|
|
133
|
+
| string_value
|
|
134
|
+
| number_value
|
|
135
|
+
| function_call
|
|
136
|
+
| var_ref
|
|
137
|
+
| paren_expr
|
|
138
|
+
|
|
139
|
+
paren_expr[object]:
|
|
140
|
+
| '(' a=expr ')' { a }
|
|
141
|
+
|
|
142
|
+
json_value[object]:
|
|
143
|
+
| '{' a=json_obj_body '}' { with_type(a, 'array') }
|
|
144
|
+
| '{' '}' { with_type({}, 'array') }
|
|
145
|
+
| '[' a=json_arr_body ']' { with_type(a, 'array') }
|
|
146
|
+
| '[' ']' { with_type([], 'array') }
|
|
147
|
+
|
|
148
|
+
json_obj_body[dict]:
|
|
149
|
+
| a=STRING ':' b=json_element ',' c=json_obj_body { self._merge_obj(a, b, c) }
|
|
150
|
+
| a=STRING ':' b=json_element { self._make_obj(a, b) }
|
|
151
|
+
|
|
152
|
+
json_arr_body[list]:
|
|
153
|
+
| a=json_element ',' b=json_arr_body { [a] + b }
|
|
154
|
+
| a=json_element { [a] }
|
|
155
|
+
|
|
156
|
+
json_element[object]:
|
|
157
|
+
| '{' a=json_obj_body '}' { dict(a) }
|
|
158
|
+
| '[' a=json_arr_body ']' { list(a) }
|
|
159
|
+
| a=STRING { self._json_string_val(a) }
|
|
160
|
+
| a=NUMBER { self._json_number_val(a) }
|
|
161
|
+
| 'true' { True }
|
|
162
|
+
| 'false' { False }
|
|
163
|
+
| 'null' { None }
|
|
164
|
+
|
|
165
|
+
const_value[object]:
|
|
166
|
+
| 'true' { with_type(True) }
|
|
167
|
+
| 'false' { with_type(False) }
|
|
168
|
+
| 'null' { with_type(None) }
|
|
169
|
+
|
|
170
|
+
regex_value[object]:
|
|
171
|
+
| a=regex_literal { with_type(a.string, 'regex') }
|
|
172
|
+
|
|
173
|
+
regex_literal[object]:
|
|
174
|
+
| a=OP { a }
|
|
175
|
+
|
|
176
|
+
string_value[object]:
|
|
177
|
+
| a=STRING { self._parse_string(a) }
|
|
178
|
+
|
|
179
|
+
number_value[object]:
|
|
180
|
+
| a=NUMBER { self._parse_number(a) }
|
|
181
|
+
|
|
182
|
+
function_call[object]:
|
|
183
|
+
| name=NAME '(' args=arg_list_inner ')' { self._call_function(name.string, args) }
|
|
184
|
+
| name=NAME '(' ')' { self._call_function(name.string, []) }
|
|
185
|
+
|
|
186
|
+
arg_list_inner[list]:
|
|
187
|
+
| a=expr ',' b=arg_list_inner { [a] + b }
|
|
188
|
+
| a=expr { [a] }
|
|
189
|
+
|
|
190
|
+
var_ref[object]:
|
|
191
|
+
| a=NAME { self._resolve_var(a.string) }
|