avrae-ls 0.4.1__py3-none-any.whl → 0.5.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.
- {avrae_ls-0.4.1.dist-info → avrae_ls-0.5.0.dist-info}/METADATA +31 -11
- avrae_ls-0.5.0.dist-info/RECORD +6 -0
- {avrae_ls-0.4.1.dist-info → avrae_ls-0.5.0.dist-info}/WHEEL +1 -2
- avrae_ls/__init__.py +0 -3
- avrae_ls/__main__.py +0 -108
- avrae_ls/alias_preview.py +0 -346
- avrae_ls/api.py +0 -2014
- avrae_ls/argparser.py +0 -430
- avrae_ls/argument_parsing.py +0 -67
- avrae_ls/code_actions.py +0 -282
- avrae_ls/codes.py +0 -3
- avrae_ls/completions.py +0 -1391
- avrae_ls/config.py +0 -496
- avrae_ls/context.py +0 -229
- avrae_ls/cvars.py +0 -115
- avrae_ls/diagnostics.py +0 -751
- avrae_ls/dice.py +0 -33
- avrae_ls/parser.py +0 -44
- avrae_ls/runtime.py +0 -661
- avrae_ls/server.py +0 -399
- avrae_ls/signature_help.py +0 -252
- avrae_ls/symbols.py +0 -266
- avrae_ls-0.4.1.dist-info/RECORD +0 -34
- avrae_ls-0.4.1.dist-info/top_level.txt +0 -2
- draconic/__init__.py +0 -4
- draconic/exceptions.py +0 -157
- draconic/helpers.py +0 -236
- draconic/interpreter.py +0 -1091
- draconic/string.py +0 -100
- draconic/types.py +0 -364
- draconic/utils.py +0 -78
- draconic/versions.py +0 -4
- {avrae_ls-0.4.1.dist-info → avrae_ls-0.5.0.dist-info}/entry_points.txt +0 -0
- {avrae_ls-0.4.1.dist-info → avrae_ls-0.5.0.dist-info}/licenses/LICENSE +0 -0
draconic/interpreter.py
DELETED
|
@@ -1,1091 +0,0 @@
|
|
|
1
|
-
import abc
|
|
2
|
-
import ast
|
|
3
|
-
import contextlib
|
|
4
|
-
from collections.abc import Mapping, Sequence
|
|
5
|
-
from functools import cached_property
|
|
6
|
-
|
|
7
|
-
from .exceptions import *
|
|
8
|
-
from .helpers import DraconicConfig, OperatorMixin, zip_star
|
|
9
|
-
from .string import check_format_spec
|
|
10
|
-
from .versions import PY_310
|
|
11
|
-
from .types import approx_len_of
|
|
12
|
-
|
|
13
|
-
__all__ = ("SimpleInterpreter", "DraconicInterpreter")
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
# ===== single-line evaluator, noncompound types, etc =====
|
|
17
|
-
class SimpleInterpreter(OperatorMixin):
|
|
18
|
-
"""A simple interpreter capable of evaluating expressions. No compound types or assignments."""
|
|
19
|
-
|
|
20
|
-
def __init__(self, builtins=None, config=None):
|
|
21
|
-
if config is None:
|
|
22
|
-
config = DraconicConfig()
|
|
23
|
-
if builtins is None:
|
|
24
|
-
builtins = {}
|
|
25
|
-
|
|
26
|
-
super().__init__(config)
|
|
27
|
-
|
|
28
|
-
if config.builtins_extend_default:
|
|
29
|
-
builtins = {**config.default_names, **builtins}
|
|
30
|
-
|
|
31
|
-
self.builtins = builtins
|
|
32
|
-
|
|
33
|
-
self.nodes = {
|
|
34
|
-
ast.Expr: self._eval_expr,
|
|
35
|
-
# literals:
|
|
36
|
-
ast.Num: self._eval_num,
|
|
37
|
-
ast.Str: self._eval_str,
|
|
38
|
-
ast.Constant: self._eval_constant,
|
|
39
|
-
ast.FormattedValue: self._eval_formattedvalue, # formatted value in f-string
|
|
40
|
-
ast.JoinedStr: self._eval_joinedstr, # f-string
|
|
41
|
-
ast.NameConstant: self._eval_constant, # True/False/None up to py3.7
|
|
42
|
-
# names:
|
|
43
|
-
ast.Name: self._eval_name,
|
|
44
|
-
# ops:
|
|
45
|
-
ast.UnaryOp: self._eval_unaryop,
|
|
46
|
-
ast.BinOp: self._eval_binop,
|
|
47
|
-
ast.BoolOp: self._eval_boolop,
|
|
48
|
-
ast.Compare: self._eval_compare,
|
|
49
|
-
ast.IfExp: self._eval_ifexp,
|
|
50
|
-
# function call:
|
|
51
|
-
ast.Call: self._eval_call,
|
|
52
|
-
ast.keyword: self._eval_keyword, # foo(x=y), kwargs (not supported)
|
|
53
|
-
# container[key]:
|
|
54
|
-
ast.Subscript: self._eval_subscript,
|
|
55
|
-
ast.Index: self._eval_index, # deprecated in py3.9 (bpo-34822)
|
|
56
|
-
ast.Slice: self._eval_slice, # deprecated in py3.9 (bpo-34822)
|
|
57
|
-
# container.key:
|
|
58
|
-
ast.Attribute: self._eval_attribute,
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
self._str = self._config.str
|
|
62
|
-
self._expr = None # save the expression for error handling
|
|
63
|
-
|
|
64
|
-
def parse(self, expr: str):
|
|
65
|
-
"""
|
|
66
|
-
Parses an expression.
|
|
67
|
-
|
|
68
|
-
:type expr: str
|
|
69
|
-
:rtype: list[ast.AST]
|
|
70
|
-
"""
|
|
71
|
-
self._expr = expr
|
|
72
|
-
try:
|
|
73
|
-
return ast.parse(expr).body
|
|
74
|
-
except SyntaxError as e:
|
|
75
|
-
raise DraconicSyntaxError(e, expr) from e
|
|
76
|
-
|
|
77
|
-
def eval(self, expr: str):
|
|
78
|
-
"""
|
|
79
|
-
Evaluates an expression.
|
|
80
|
-
|
|
81
|
-
:type expr: list[ast.AST] or str
|
|
82
|
-
"""
|
|
83
|
-
expr = self.parse(expr)
|
|
84
|
-
|
|
85
|
-
self._preflight()
|
|
86
|
-
try:
|
|
87
|
-
expression = expr[0]
|
|
88
|
-
except IndexError: # if there is no expression, evaluate to None
|
|
89
|
-
return None
|
|
90
|
-
return self._eval(expression)
|
|
91
|
-
|
|
92
|
-
def _eval(self, node):
|
|
93
|
-
"""The internal evaluator used on each node in the parsed tree."""
|
|
94
|
-
try:
|
|
95
|
-
handler = self.nodes[type(node)]
|
|
96
|
-
except KeyError:
|
|
97
|
-
raise FeatureNotAvailable(
|
|
98
|
-
"Sorry, {0} is not available in this evaluator".format(type(node).__name__), node, self._expr
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
try:
|
|
102
|
-
return handler(node)
|
|
103
|
-
except _PostponedRaise as pr:
|
|
104
|
-
raise pr.cls(*pr.args, **pr.kwargs, node=node, expr=self._expr)
|
|
105
|
-
except DraconicException:
|
|
106
|
-
raise
|
|
107
|
-
except Exception as e:
|
|
108
|
-
raise AnnotatedException(e, node, self._expr) from e
|
|
109
|
-
|
|
110
|
-
def _preflight(self):
|
|
111
|
-
"""Called before starting evaluation."""
|
|
112
|
-
pass
|
|
113
|
-
|
|
114
|
-
@property
|
|
115
|
-
def names(self):
|
|
116
|
-
return self.builtins
|
|
117
|
-
|
|
118
|
-
@names.setter
|
|
119
|
-
def names(self, new_names):
|
|
120
|
-
self.builtins = new_names
|
|
121
|
-
|
|
122
|
-
# ===== nodes =====
|
|
123
|
-
def _eval_expr(self, node):
|
|
124
|
-
return self._eval(node.value)
|
|
125
|
-
|
|
126
|
-
@staticmethod
|
|
127
|
-
def _eval_num(node):
|
|
128
|
-
return node.n
|
|
129
|
-
|
|
130
|
-
def _eval_str(self, node):
|
|
131
|
-
if len(node.s) > self._config.max_const_len:
|
|
132
|
-
raise IterableTooLong(
|
|
133
|
-
f"String literal in statement is too long ({len(node.s)} > {self._config.max_const_len})",
|
|
134
|
-
node,
|
|
135
|
-
self._expr,
|
|
136
|
-
)
|
|
137
|
-
return self._str(node.s)
|
|
138
|
-
|
|
139
|
-
def _eval_constant(self, node):
|
|
140
|
-
if hasattr(node.value, "__len__") and len(node.value) > self._config.max_const_len:
|
|
141
|
-
raise IterableTooLong(
|
|
142
|
-
f"Literal in statement is too long ({len(node.value)} > {self._config.max_const_len})", node, self._expr
|
|
143
|
-
)
|
|
144
|
-
if isinstance(node.value, bytes):
|
|
145
|
-
raise FeatureNotAvailable("Creation of bytes literals is not allowed", node, self._expr)
|
|
146
|
-
return node.value
|
|
147
|
-
|
|
148
|
-
def _eval_unaryop(self, node):
|
|
149
|
-
return self.operators[type(node.op)](self._eval(node.operand))
|
|
150
|
-
|
|
151
|
-
def _eval_binop(self, node):
|
|
152
|
-
return self.operators[type(node.op)](self._eval(node.left), self._eval(node.right))
|
|
153
|
-
|
|
154
|
-
def _eval_boolop(self, node):
|
|
155
|
-
vout = False
|
|
156
|
-
if isinstance(node.op, ast.And):
|
|
157
|
-
for value in node.values:
|
|
158
|
-
vout = self._eval(value)
|
|
159
|
-
if not vout:
|
|
160
|
-
return vout
|
|
161
|
-
elif isinstance(node.op, ast.Or):
|
|
162
|
-
for value in node.values:
|
|
163
|
-
vout = self._eval(value)
|
|
164
|
-
if vout:
|
|
165
|
-
return vout
|
|
166
|
-
return vout
|
|
167
|
-
|
|
168
|
-
def _eval_compare(self, node):
|
|
169
|
-
right = self._eval(node.left)
|
|
170
|
-
to_return = True
|
|
171
|
-
for operation, comp in zip(node.ops, node.comparators):
|
|
172
|
-
if not to_return:
|
|
173
|
-
break
|
|
174
|
-
left = right
|
|
175
|
-
right = self._eval(comp)
|
|
176
|
-
to_return = self.operators[type(operation)](left, right)
|
|
177
|
-
return to_return
|
|
178
|
-
|
|
179
|
-
def _eval_ifexp(self, node):
|
|
180
|
-
return self._eval(node.body) if self._eval(node.test) else self._eval(node.orelse)
|
|
181
|
-
|
|
182
|
-
def _eval_call(self, node):
|
|
183
|
-
func = self._eval(node.func)
|
|
184
|
-
return func(*(self._eval(a) for a in node.args), **dict(self._eval(k) for k in node.keywords))
|
|
185
|
-
|
|
186
|
-
def _eval_keyword(self, node):
|
|
187
|
-
return node.arg, self._eval(node.value)
|
|
188
|
-
|
|
189
|
-
def _eval_name(self, node):
|
|
190
|
-
try:
|
|
191
|
-
return self.names[node.id]
|
|
192
|
-
except KeyError:
|
|
193
|
-
raise NotDefined(f"{node.id} is not defined", node, self._expr)
|
|
194
|
-
|
|
195
|
-
def _eval_subscript(self, node):
|
|
196
|
-
container = self._eval(node.value)
|
|
197
|
-
key = self._eval(node.slice)
|
|
198
|
-
try:
|
|
199
|
-
return container[key]
|
|
200
|
-
except KeyError:
|
|
201
|
-
raise
|
|
202
|
-
|
|
203
|
-
def _eval_attribute(self, node):
|
|
204
|
-
for prefix in self._config.disallow_prefixes:
|
|
205
|
-
if node.attr.startswith(prefix):
|
|
206
|
-
raise FeatureNotAvailable(f"Access to the {node.attr} attribute is not allowed", node, self._expr)
|
|
207
|
-
if node.attr in self._config.disallow_methods:
|
|
208
|
-
raise FeatureNotAvailable(f"Access to the {node.attr} attribute is not allowed", node, self._expr)
|
|
209
|
-
# eval node
|
|
210
|
-
node_evaluated = self._eval(node.value)
|
|
211
|
-
|
|
212
|
-
# Maybe the base object is an actual object, not just a dict
|
|
213
|
-
try:
|
|
214
|
-
return getattr(node_evaluated, node.attr)
|
|
215
|
-
except (AttributeError, TypeError):
|
|
216
|
-
# If it is not present, raise an exception
|
|
217
|
-
raise NotDefined(f"'{type(node_evaluated).__name__}' object has no attribute {node.attr}", node, self._expr)
|
|
218
|
-
|
|
219
|
-
def _eval_index(self, node):
|
|
220
|
-
return self._eval(node.value)
|
|
221
|
-
|
|
222
|
-
def _eval_slice(self, node):
|
|
223
|
-
lower = upper = step = None
|
|
224
|
-
if node.lower is not None:
|
|
225
|
-
lower = self._eval(node.lower)
|
|
226
|
-
if node.upper is not None:
|
|
227
|
-
upper = self._eval(node.upper)
|
|
228
|
-
if node.step is not None:
|
|
229
|
-
step = self._eval(node.step)
|
|
230
|
-
return slice(lower, upper, step)
|
|
231
|
-
|
|
232
|
-
def _eval_joinedstr(self, node):
|
|
233
|
-
length = 0
|
|
234
|
-
evaluated_values = []
|
|
235
|
-
for n in node.values:
|
|
236
|
-
val = str(self._eval(n))
|
|
237
|
-
length += len(val)
|
|
238
|
-
if length > self._config.max_const_len:
|
|
239
|
-
raise IterableTooLong(
|
|
240
|
-
f"f-string in statement is too long ({length} > {self._config.max_const_len})", node, self._expr
|
|
241
|
-
)
|
|
242
|
-
evaluated_values.append(val)
|
|
243
|
-
return "".join(evaluated_values)
|
|
244
|
-
|
|
245
|
-
def _eval_formattedvalue(self, node):
|
|
246
|
-
if node.format_spec:
|
|
247
|
-
format_spec = str(self._eval(node.format_spec))
|
|
248
|
-
check_format_spec(self._config, format_spec)
|
|
249
|
-
return self._str(format(self._eval(node.value), format_spec))
|
|
250
|
-
return self._eval(node.value)
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
# ===== multiple-line execution, assignment, compound types =====
|
|
254
|
-
class _Break:
|
|
255
|
-
__slots__ = ("node",)
|
|
256
|
-
|
|
257
|
-
def __init__(self, node: ast.Break):
|
|
258
|
-
self.node = node
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
class _Continue:
|
|
262
|
-
__slots__ = ("node",)
|
|
263
|
-
|
|
264
|
-
def __init__(self, node: ast.Continue):
|
|
265
|
-
self.node = node
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
class _Return:
|
|
269
|
-
__slots__ = ("value", "node")
|
|
270
|
-
|
|
271
|
-
def __init__(self, retval, node: ast.Return):
|
|
272
|
-
self.value = retval
|
|
273
|
-
self.node = node
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
class _Callable(abc.ABC):
|
|
277
|
-
"""ABC for functions and lambdas"""
|
|
278
|
-
|
|
279
|
-
def __init__(self, interpreter, node, names_at_def, defining_expr):
|
|
280
|
-
self._interpreter = interpreter
|
|
281
|
-
self._node = node
|
|
282
|
-
self._outer_scope_names = names_at_def
|
|
283
|
-
self._defining_expr = defining_expr
|
|
284
|
-
|
|
285
|
-
# faux introspection props since __dunders__ aren't accessible
|
|
286
|
-
@property
|
|
287
|
-
def name(self):
|
|
288
|
-
return self.__name__
|
|
289
|
-
|
|
290
|
-
@cached_property
|
|
291
|
-
def doc(self):
|
|
292
|
-
return ast.get_docstring(self._node)
|
|
293
|
-
|
|
294
|
-
def __call__(self, *args, **kwargs):
|
|
295
|
-
raise NotImplementedError
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
class _Function(_Callable):
|
|
299
|
-
"""A wrapper class around an ast.FunctionDef."""
|
|
300
|
-
|
|
301
|
-
def __init__(self, interpreter, functiondef, names_at_def, defining_expr):
|
|
302
|
-
super().__init__(interpreter, functiondef, names_at_def, defining_expr)
|
|
303
|
-
self.__name__ = self._name = functiondef.name
|
|
304
|
-
|
|
305
|
-
def __repr__(self):
|
|
306
|
-
return f"<Function {self._name}>"
|
|
307
|
-
|
|
308
|
-
def __call__(self, *args, **kwargs):
|
|
309
|
-
try:
|
|
310
|
-
# noinspection PyProtectedMember
|
|
311
|
-
return self._interpreter._exec_function(self, *args, **kwargs)
|
|
312
|
-
except DraconicException as e:
|
|
313
|
-
e.__drac_context__ = self._name
|
|
314
|
-
raise
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
class _Lambda(_Callable):
|
|
318
|
-
"""A wrapper class around an ast.Lambda."""
|
|
319
|
-
|
|
320
|
-
def __init__(self, interpreter, lambdadef, names_at_def, defining_expr):
|
|
321
|
-
super().__init__(interpreter, lambdadef, names_at_def, defining_expr)
|
|
322
|
-
self.__name__ = self._name = "<lambda>"
|
|
323
|
-
|
|
324
|
-
def __repr__(self):
|
|
325
|
-
return f"<Function <lambda>>"
|
|
326
|
-
|
|
327
|
-
@property
|
|
328
|
-
def doc(self):
|
|
329
|
-
return None
|
|
330
|
-
|
|
331
|
-
def __call__(self, *args, **kwargs):
|
|
332
|
-
try:
|
|
333
|
-
# noinspection PyProtectedMember
|
|
334
|
-
return self._interpreter._exec_lambda(self, *args, **kwargs)
|
|
335
|
-
except DraconicException as e:
|
|
336
|
-
e.__drac_context__ = self._name
|
|
337
|
-
raise
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
class DraconicInterpreter(SimpleInterpreter):
|
|
341
|
-
"""The Draconic interpreter. Capable of running Draconic code."""
|
|
342
|
-
|
|
343
|
-
def __init__(self, builtins=None, config=None, initial_names=None):
|
|
344
|
-
super().__init__(builtins, config)
|
|
345
|
-
|
|
346
|
-
if initial_names is None:
|
|
347
|
-
initial_names = {}
|
|
348
|
-
|
|
349
|
-
self.nodes.update(
|
|
350
|
-
{
|
|
351
|
-
# compound types:
|
|
352
|
-
ast.Dict: self._eval_dict,
|
|
353
|
-
ast.Tuple: self._eval_tuple,
|
|
354
|
-
ast.List: self._eval_list,
|
|
355
|
-
ast.Set: self._eval_set,
|
|
356
|
-
# comprehensions:
|
|
357
|
-
ast.ListComp: self._eval_listcomp,
|
|
358
|
-
ast.SetComp: self._eval_setcomp,
|
|
359
|
-
ast.DictComp: self._eval_dictcomp,
|
|
360
|
-
ast.GeneratorExp: self._eval_generatorexp,
|
|
361
|
-
ast.Starred: self._eval_starred, # foo(*iterable), [*iterable], etc.
|
|
362
|
-
# assignments:
|
|
363
|
-
ast.Assign: self._eval_assign,
|
|
364
|
-
ast.AugAssign: self._eval_augassign,
|
|
365
|
-
ast.NamedExpr: self._eval_namedexpr,
|
|
366
|
-
# control:
|
|
367
|
-
ast.Return: self._exec_return,
|
|
368
|
-
ast.If: self._exec_if,
|
|
369
|
-
ast.For: self._exec_for,
|
|
370
|
-
ast.While: self._exec_while,
|
|
371
|
-
ast.Break: lambda node: _Break(node),
|
|
372
|
-
ast.Continue: lambda node: _Continue(node),
|
|
373
|
-
ast.Pass: lambda node: None,
|
|
374
|
-
# functions:
|
|
375
|
-
ast.FunctionDef: self._eval_functiondef,
|
|
376
|
-
ast.Lambda: self._eval_lambda,
|
|
377
|
-
# try/except:
|
|
378
|
-
ast.Try: self._exec_try,
|
|
379
|
-
}
|
|
380
|
-
)
|
|
381
|
-
|
|
382
|
-
self.assign_nodes = {
|
|
383
|
-
ast.Name: self._assign_name,
|
|
384
|
-
ast.Tuple: self._assign_unpack,
|
|
385
|
-
ast.List: self._assign_unpack,
|
|
386
|
-
ast.Subscript: self._assign_subscript,
|
|
387
|
-
# no assigning to attributes
|
|
388
|
-
ast.Starred: self._assign_starred, # a, *b = [x, y, z]
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
self.patma_nodes = {}
|
|
392
|
-
|
|
393
|
-
if PY_310:
|
|
394
|
-
self.nodes.update(
|
|
395
|
-
{
|
|
396
|
-
ast.Match: self._exec_match,
|
|
397
|
-
}
|
|
398
|
-
)
|
|
399
|
-
|
|
400
|
-
self.patma_nodes.update(
|
|
401
|
-
{
|
|
402
|
-
ast.MatchValue: self._patma_match_value,
|
|
403
|
-
ast.MatchSingleton: self._patma_match_singleton,
|
|
404
|
-
ast.MatchSequence: self._patma_match_sequence,
|
|
405
|
-
ast.MatchMapping: self._patma_match_mapping,
|
|
406
|
-
ast.MatchStar: self._patma_match_star,
|
|
407
|
-
# no MatchClass
|
|
408
|
-
ast.MatchAs: self._patma_match_as,
|
|
409
|
-
ast.MatchOr: self._patma_match_or,
|
|
410
|
-
}
|
|
411
|
-
)
|
|
412
|
-
|
|
413
|
-
# compound type helpers
|
|
414
|
-
self._list = self._config.list
|
|
415
|
-
self._set = self._config.set
|
|
416
|
-
self._dict = self._config.dict
|
|
417
|
-
|
|
418
|
-
self._num_stmts = 0
|
|
419
|
-
self._loops = 0
|
|
420
|
-
self._depth = 1
|
|
421
|
-
self._names = initial_names
|
|
422
|
-
|
|
423
|
-
def eval(self, expr: str):
|
|
424
|
-
retval = super().eval(expr)
|
|
425
|
-
if isinstance(retval, _Return):
|
|
426
|
-
return retval.value
|
|
427
|
-
elif isinstance(retval, (_Break, _Continue)):
|
|
428
|
-
raise DraconicSyntaxError.from_node(retval.node, msg="Loop control outside loop", expr=self._expr)
|
|
429
|
-
return retval
|
|
430
|
-
|
|
431
|
-
def execute(self, expr: str):
|
|
432
|
-
"""Executes an AST body."""
|
|
433
|
-
expr = self.parse(expr)
|
|
434
|
-
|
|
435
|
-
self._preflight()
|
|
436
|
-
retval = self._exec(expr)
|
|
437
|
-
if isinstance(retval, (_Break, _Continue)):
|
|
438
|
-
raise DraconicSyntaxError.from_node(retval.node, msg="Loop control outside loop", expr=self._expr)
|
|
439
|
-
if isinstance(retval, _Return):
|
|
440
|
-
return retval.value
|
|
441
|
-
|
|
442
|
-
def execute_module(self, expr: str, module_name="<module>"):
|
|
443
|
-
"""
|
|
444
|
-
Executes the expression as if it was a module.
|
|
445
|
-
This is similar to *execute* except:
|
|
446
|
-
- it doesn't allow bare returns
|
|
447
|
-
- it doesn't call preflight
|
|
448
|
-
- it saves the previously running expression
|
|
449
|
-
- it sets the exception context
|
|
450
|
-
"""
|
|
451
|
-
old_expr = self._expr
|
|
452
|
-
try:
|
|
453
|
-
expr = self.parse(expr)
|
|
454
|
-
retval = self._exec(expr)
|
|
455
|
-
if isinstance(retval, (_Break, _Continue)):
|
|
456
|
-
raise DraconicSyntaxError.from_node(retval.node, msg="Loop control outside loop", expr=self._expr)
|
|
457
|
-
if isinstance(retval, _Return):
|
|
458
|
-
raise DraconicSyntaxError.from_node(retval.node, msg="'return' outside function", expr=self._expr)
|
|
459
|
-
except DraconicException as e:
|
|
460
|
-
e.__drac_context__ = module_name
|
|
461
|
-
raise
|
|
462
|
-
finally:
|
|
463
|
-
self._expr = old_expr
|
|
464
|
-
|
|
465
|
-
def _preflight(self):
|
|
466
|
-
self._num_stmts = 0
|
|
467
|
-
self._loops = 0
|
|
468
|
-
super()._preflight()
|
|
469
|
-
|
|
470
|
-
def _eval(self, node):
|
|
471
|
-
self._num_stmts += 1
|
|
472
|
-
if self._num_stmts > self._config.max_statements:
|
|
473
|
-
raise TooManyStatements("You are trying to execute too many statements.", node, self._expr)
|
|
474
|
-
|
|
475
|
-
val = super()._eval(node)
|
|
476
|
-
# ensure that it's always an instance of our safe compound types being returned
|
|
477
|
-
# note: makes a copy, so the original copy won't be updated
|
|
478
|
-
# we don't use isinstance because we're looking for very specific classes
|
|
479
|
-
if type(val) is str:
|
|
480
|
-
return self._str(val)
|
|
481
|
-
elif type(val) is list:
|
|
482
|
-
return self._list(val)
|
|
483
|
-
elif type(val) is dict:
|
|
484
|
-
return self._dict(val)
|
|
485
|
-
elif type(val) is set:
|
|
486
|
-
return self._set(val)
|
|
487
|
-
return val
|
|
488
|
-
|
|
489
|
-
def _exec(self, body):
|
|
490
|
-
for expression in body:
|
|
491
|
-
retval = self._eval(expression)
|
|
492
|
-
if isinstance(retval, (_Return, _Break, _Continue)):
|
|
493
|
-
return retval
|
|
494
|
-
|
|
495
|
-
@property
|
|
496
|
-
def names(self):
|
|
497
|
-
return {**self.builtins, **self._names}
|
|
498
|
-
|
|
499
|
-
@names.setter
|
|
500
|
-
def names(self, new_names):
|
|
501
|
-
self._names = new_names
|
|
502
|
-
|
|
503
|
-
# ===== compound types =====
|
|
504
|
-
def _eval_dict(self, node):
|
|
505
|
-
return self._dict(self._starred_keyword_unwrap(zip(node.keys, node.values)))
|
|
506
|
-
|
|
507
|
-
def _eval_tuple(self, node):
|
|
508
|
-
return tuple(self._starred_unwrap(node.elts))
|
|
509
|
-
|
|
510
|
-
def _eval_list(self, node):
|
|
511
|
-
return self._list(self._starred_unwrap(node.elts))
|
|
512
|
-
|
|
513
|
-
def _eval_set(self, node):
|
|
514
|
-
return self._set(self._starred_unwrap(node.elts))
|
|
515
|
-
|
|
516
|
-
def _eval_listcomp(self, node):
|
|
517
|
-
return self._list(self._do_comprehension(node))
|
|
518
|
-
|
|
519
|
-
def _eval_setcomp(self, node):
|
|
520
|
-
return self._set(self._do_comprehension(node))
|
|
521
|
-
|
|
522
|
-
def _eval_dictcomp(self, node):
|
|
523
|
-
return self._dict(self._do_comprehension(node, is_dictcomp=True))
|
|
524
|
-
|
|
525
|
-
def _eval_generatorexp(self, node):
|
|
526
|
-
for item in self._do_comprehension(node):
|
|
527
|
-
yield item
|
|
528
|
-
|
|
529
|
-
def _do_comprehension(self, comprehension_node, is_dictcomp=False):
|
|
530
|
-
if is_dictcomp:
|
|
531
|
-
|
|
532
|
-
def do_value(node):
|
|
533
|
-
return self._eval(node.key), self._eval(node.value)
|
|
534
|
-
|
|
535
|
-
else:
|
|
536
|
-
|
|
537
|
-
def do_value(node):
|
|
538
|
-
return self._eval(node.elt)
|
|
539
|
-
|
|
540
|
-
extra_names = {}
|
|
541
|
-
previous_name_evaller = self.nodes[ast.Name]
|
|
542
|
-
|
|
543
|
-
def eval_names_extra(node):
|
|
544
|
-
"""
|
|
545
|
-
Here we hide our extra scope for within this comprehension
|
|
546
|
-
"""
|
|
547
|
-
if node.id in extra_names:
|
|
548
|
-
return extra_names[node.id]
|
|
549
|
-
return previous_name_evaller(node)
|
|
550
|
-
|
|
551
|
-
self.nodes.update({ast.Name: eval_names_extra})
|
|
552
|
-
|
|
553
|
-
def recurse_targets(target, value):
|
|
554
|
-
"""
|
|
555
|
-
Recursively (enter, (into, (nested, name), unpacking)) = \
|
|
556
|
-
and, (assign, (values, to), each
|
|
557
|
-
"""
|
|
558
|
-
if isinstance(target, ast.Name):
|
|
559
|
-
extra_names[target.id] = value
|
|
560
|
-
else:
|
|
561
|
-
for t, v in zip(target.elts, value):
|
|
562
|
-
recurse_targets(t, v)
|
|
563
|
-
|
|
564
|
-
def do_generator(gi=0, total_len=0):
|
|
565
|
-
"""
|
|
566
|
-
For each generator, set the names used in the final emitted value/the next generator.
|
|
567
|
-
Only the final generator (gi = len(comprehension_node.generator)-1) should emit the final values,
|
|
568
|
-
since only then are all possible necessary values set in extra_names.
|
|
569
|
-
"""
|
|
570
|
-
generator_node = comprehension_node.generators[gi]
|
|
571
|
-
for i in self._eval(generator_node.iter):
|
|
572
|
-
self._loops += 1
|
|
573
|
-
if self._loops > self._config.max_loops:
|
|
574
|
-
raise IterableTooLong("Comprehension generates too many elements", comprehension_node, self._expr)
|
|
575
|
-
|
|
576
|
-
# set names
|
|
577
|
-
recurse_targets(generator_node.target, i)
|
|
578
|
-
|
|
579
|
-
if all(self._eval(iff) for iff in generator_node.ifs):
|
|
580
|
-
if len(comprehension_node.generators) > gi + 1:
|
|
581
|
-
# next generator
|
|
582
|
-
yield from do_generator(gi + 1, total_len) # bubble up emitted values
|
|
583
|
-
else:
|
|
584
|
-
# emit values
|
|
585
|
-
value = do_value(comprehension_node)
|
|
586
|
-
total_len += sum(approx_len_of(val) for val in value) if is_dictcomp else approx_len_of(value)
|
|
587
|
-
total_len += 1
|
|
588
|
-
if total_len > self._config.max_const_len:
|
|
589
|
-
raise IterableTooLong("Comprehension generates too much", comprehension_node, self._expr)
|
|
590
|
-
yield value
|
|
591
|
-
|
|
592
|
-
try:
|
|
593
|
-
yield from do_generator()
|
|
594
|
-
finally:
|
|
595
|
-
self.nodes.update({ast.Name: previous_name_evaller})
|
|
596
|
-
|
|
597
|
-
def _eval_starred(self, node):
|
|
598
|
-
raise DraconicSyntaxError.from_node(node, "can't use starred expression here", self._expr)
|
|
599
|
-
|
|
600
|
-
def _starred_unwrap(self, nodes, *, check_len=True):
|
|
601
|
-
total_len = 0
|
|
602
|
-
|
|
603
|
-
for node in nodes:
|
|
604
|
-
if type(node) is ast.Starred:
|
|
605
|
-
evalue = self._eval(node.value)
|
|
606
|
-
try:
|
|
607
|
-
for retval in evalue:
|
|
608
|
-
self._loops += 1
|
|
609
|
-
if self._loops > self._config.max_loops:
|
|
610
|
-
raise IterableTooLong("Unwrapping generates too many elements", node, self._expr)
|
|
611
|
-
if check_len:
|
|
612
|
-
total_len += approx_len_of(retval) + 1
|
|
613
|
-
if total_len > self._config.max_const_len:
|
|
614
|
-
raise IterableTooLong("Unwrapping generates too much", node, self._expr)
|
|
615
|
-
yield retval
|
|
616
|
-
except TypeError:
|
|
617
|
-
raise TypeError(f"Value after * must be iterable, got {type(evalue).__name__}")
|
|
618
|
-
else:
|
|
619
|
-
retval = self._eval(node)
|
|
620
|
-
if check_len:
|
|
621
|
-
total_len += approx_len_of(retval) + 1
|
|
622
|
-
if total_len > self._config.max_const_len:
|
|
623
|
-
raise IterableTooLong("Unwrapping generates too much", node, self._expr)
|
|
624
|
-
yield retval
|
|
625
|
-
|
|
626
|
-
def _starred_keyword_unwrap(self, items, *, check_len=True):
|
|
627
|
-
total_len = 0
|
|
628
|
-
|
|
629
|
-
for key, value in items:
|
|
630
|
-
evalue = self._eval(value)
|
|
631
|
-
if key is None:
|
|
632
|
-
if isinstance(evalue, Mapping):
|
|
633
|
-
for retval in evalue.items():
|
|
634
|
-
self._loops += 1
|
|
635
|
-
if self._loops > self._config.max_loops:
|
|
636
|
-
raise IterableTooLong("Unwrapping generates too many elements", value, self._expr)
|
|
637
|
-
if check_len:
|
|
638
|
-
total_len += sum(approx_len_of(val) for val in retval) + 1
|
|
639
|
-
if total_len > self._config.max_const_len:
|
|
640
|
-
raise IterableTooLong("Unwrapping generates too much", value, self._expr)
|
|
641
|
-
yield retval
|
|
642
|
-
else:
|
|
643
|
-
raise TypeError(f"argument after ** must be a mapping, got {type(value).__name__}")
|
|
644
|
-
else:
|
|
645
|
-
retval = self._eval(key) if isinstance(key, ast.AST) else key, evalue
|
|
646
|
-
if check_len:
|
|
647
|
-
total_len += sum(approx_len_of(val) for val in retval) + 1
|
|
648
|
-
if total_len > self._config.max_const_len:
|
|
649
|
-
raise IterableTooLong("Unwrapping generates too much", value, self._expr)
|
|
650
|
-
yield retval
|
|
651
|
-
|
|
652
|
-
# ===== assignments =====
|
|
653
|
-
def _eval_assign(self, node):
|
|
654
|
-
value = self._eval(node.value)
|
|
655
|
-
for target in node.targets: # a = b = 1
|
|
656
|
-
self._assign(target, value)
|
|
657
|
-
|
|
658
|
-
def _eval_augassign(self, node):
|
|
659
|
-
target = node.target
|
|
660
|
-
# transform a += 1 to a = a + 1, then we can use assign and eval
|
|
661
|
-
new_value = ast.BinOp(left=target, op=node.op, right=node.value)
|
|
662
|
-
ast.copy_location(new_value, target)
|
|
663
|
-
self._assign(target, self._eval_binop(new_value))
|
|
664
|
-
|
|
665
|
-
def _eval_namedexpr(self, node):
|
|
666
|
-
value = self._eval(node.value)
|
|
667
|
-
self._assign(node.target, value)
|
|
668
|
-
return value
|
|
669
|
-
|
|
670
|
-
# ---- primary assign branch ----
|
|
671
|
-
def _assign(self, names, values):
|
|
672
|
-
try:
|
|
673
|
-
handler = self.assign_nodes[type(names)]
|
|
674
|
-
except KeyError:
|
|
675
|
-
raise FeatureNotAvailable(f"Assignment to {type(names).__name__} is not allowed", names, self._expr)
|
|
676
|
-
# noinspection PyArgumentList
|
|
677
|
-
return handler(names, values)
|
|
678
|
-
|
|
679
|
-
def _assign_name(self, name, value):
|
|
680
|
-
if name.id in self.builtins:
|
|
681
|
-
raise DraconicValueError(f"{name.id} is already builtin (no shadow assignments).", name, self._expr)
|
|
682
|
-
self._names[name.id] = value
|
|
683
|
-
|
|
684
|
-
def _assign_subscript(self, name, value):
|
|
685
|
-
container = self._eval(name.value)
|
|
686
|
-
key = self._eval(name.slice)
|
|
687
|
-
container[key] = value # no further evaluation needed, if container is in names it will update
|
|
688
|
-
|
|
689
|
-
def _assign_unpack(self, names, values):
|
|
690
|
-
if not isinstance(names, (ast.Tuple, ast.List)):
|
|
691
|
-
self._assign(names, values)
|
|
692
|
-
else:
|
|
693
|
-
try:
|
|
694
|
-
values = list(iter(values))
|
|
695
|
-
except TypeError:
|
|
696
|
-
raise DraconicValueError(
|
|
697
|
-
f"Cannot unpack non-iterable {type(values).__name__} object", names, self._expr
|
|
698
|
-
)
|
|
699
|
-
|
|
700
|
-
stars = (i for i in names.elts if type(i) is ast.Starred)
|
|
701
|
-
starred = next(stars, None)
|
|
702
|
-
|
|
703
|
-
if starred is None:
|
|
704
|
-
if len(names.elts) > len(values):
|
|
705
|
-
raise DraconicValueError(
|
|
706
|
-
f"not enough values to unpack (expected {len(names.elts)}, got {len(values)})",
|
|
707
|
-
names,
|
|
708
|
-
self._expr,
|
|
709
|
-
)
|
|
710
|
-
elif len(names.elts) < len(values):
|
|
711
|
-
raise DraconicValueError(
|
|
712
|
-
f"too many values to unpack (expected {len(names.elts)}, got {len(values)})",
|
|
713
|
-
names,
|
|
714
|
-
self._expr,
|
|
715
|
-
)
|
|
716
|
-
for t, v in zip(names.elts, values):
|
|
717
|
-
self._assign_unpack(t, v)
|
|
718
|
-
elif (extra := next(stars, None)) is not None:
|
|
719
|
-
raise DraconicSyntaxError.from_node(extra, "multiple starred expressions in assignment", self._expr)
|
|
720
|
-
else:
|
|
721
|
-
if len(values) < (len(names.elts) - 1):
|
|
722
|
-
raise DraconicValueError(
|
|
723
|
-
f"not enough values to unpack (expected at least {len(names.elts) - 1}, got {len(values)})",
|
|
724
|
-
names,
|
|
725
|
-
self._expr,
|
|
726
|
-
)
|
|
727
|
-
|
|
728
|
-
for t, v in zip_star(names.elts, values, star_index=names.elts.index(starred)):
|
|
729
|
-
self._assign_unpack(t, v)
|
|
730
|
-
|
|
731
|
-
def _assign_starred(self, name, value):
|
|
732
|
-
self._assign_name(name.value, value)
|
|
733
|
-
|
|
734
|
-
# ===== execution =====
|
|
735
|
-
def _exec_return(self, node):
|
|
736
|
-
retval = self._eval(node.value) if node.value is not None else None
|
|
737
|
-
return _Return(retval, node)
|
|
738
|
-
|
|
739
|
-
def _exec_if(self, node):
|
|
740
|
-
test = self._eval(node.test)
|
|
741
|
-
if test:
|
|
742
|
-
return self._exec(node.body)
|
|
743
|
-
else:
|
|
744
|
-
return self._exec(node.orelse)
|
|
745
|
-
|
|
746
|
-
def _exec_for(self, node):
|
|
747
|
-
for item in self._eval(node.iter):
|
|
748
|
-
self._loops += 1
|
|
749
|
-
if self._loops > self._config.max_loops:
|
|
750
|
-
raise TooManyStatements("Too many loops (in for block)", node, self._expr)
|
|
751
|
-
|
|
752
|
-
self._assign(node.target, item)
|
|
753
|
-
retval = self._exec(node.body)
|
|
754
|
-
if isinstance(retval, _Return):
|
|
755
|
-
return retval
|
|
756
|
-
elif isinstance(retval, _Break):
|
|
757
|
-
break
|
|
758
|
-
elif isinstance(retval, _Continue):
|
|
759
|
-
continue
|
|
760
|
-
else:
|
|
761
|
-
return self._exec(node.orelse)
|
|
762
|
-
|
|
763
|
-
def _exec_while(self, node):
|
|
764
|
-
while self._eval(node.test):
|
|
765
|
-
self._loops += 1
|
|
766
|
-
if self._loops > self._config.max_loops:
|
|
767
|
-
raise TooManyStatements("Too many loops (in while block)", node, self._expr)
|
|
768
|
-
|
|
769
|
-
retval = self._exec(node.body)
|
|
770
|
-
if isinstance(retval, _Return):
|
|
771
|
-
return retval
|
|
772
|
-
elif isinstance(retval, _Break):
|
|
773
|
-
break
|
|
774
|
-
elif isinstance(retval, _Continue):
|
|
775
|
-
continue
|
|
776
|
-
else:
|
|
777
|
-
return self._exec(node.orelse)
|
|
778
|
-
|
|
779
|
-
# ===== patma =====
|
|
780
|
-
# impl inspired by GVR's impl at https://github.com/gvanrossum/patma/blob/master/patma.py
|
|
781
|
-
# note: we do duplicate binding checks at runtime instead of preflight, which means that
|
|
782
|
-
# some code could run before the duplicate binding is detected, and certain exprs illegal in Python are legal here
|
|
783
|
-
# this is OK for our use case but differs from Python's impl
|
|
784
|
-
def _exec_match(self, node):
|
|
785
|
-
subject = self._eval(node.subject)
|
|
786
|
-
for match_case in node.cases:
|
|
787
|
-
if (bindings := self._patma(match_case.pattern, subject)) is not None:
|
|
788
|
-
self._names.update(bindings) # In python patma, values are bound before the guard executes
|
|
789
|
-
if match_case.guard is not None and not self._eval(match_case.guard):
|
|
790
|
-
continue
|
|
791
|
-
return self._exec(match_case.body)
|
|
792
|
-
|
|
793
|
-
def _patma(self, pattern, subject):
|
|
794
|
-
"""
|
|
795
|
-
Execute matching logic for a given match_case and subject.
|
|
796
|
-
If the subject matches the case, return the dict of bindings for this case.
|
|
797
|
-
Otherwise, return None.
|
|
798
|
-
"""
|
|
799
|
-
try:
|
|
800
|
-
handler = self.patma_nodes[type(pattern)]
|
|
801
|
-
except KeyError:
|
|
802
|
-
raise FeatureNotAvailable(f"Matching on {type(pattern).__name__} is not allowed", pattern, self._expr)
|
|
803
|
-
self._num_stmts += 1
|
|
804
|
-
return handler(pattern, subject)
|
|
805
|
-
|
|
806
|
-
def _patma_match_value(self, node, subject):
|
|
807
|
-
if subject == self._eval(node.value):
|
|
808
|
-
return {}
|
|
809
|
-
return None
|
|
810
|
-
|
|
811
|
-
@staticmethod
|
|
812
|
-
def _patma_match_singleton(node, subject):
|
|
813
|
-
if subject is node.value:
|
|
814
|
-
return {}
|
|
815
|
-
return None
|
|
816
|
-
|
|
817
|
-
def _patma_match_sequence(self, node, subject):
|
|
818
|
-
if not isinstance(subject, Sequence) or isinstance(subject, (str, bytes)):
|
|
819
|
-
return None
|
|
820
|
-
|
|
821
|
-
match_star_idxs = [idx for idx, pattern in enumerate(node.patterns) if isinstance(pattern, ast.MatchStar)]
|
|
822
|
-
if len(match_star_idxs) > 1:
|
|
823
|
-
# multiple starred names
|
|
824
|
-
raise DraconicValueError(f"multiple starred names in sequence pattern", node, self._expr)
|
|
825
|
-
elif match_star_idxs:
|
|
826
|
-
# one starred name
|
|
827
|
-
if not len(node.patterns) <= len(subject) + 1:
|
|
828
|
-
return None
|
|
829
|
-
pattern_iterator = zip_star(node.patterns, subject, star_index=match_star_idxs[0])
|
|
830
|
-
else:
|
|
831
|
-
# no starred names
|
|
832
|
-
if len(node.patterns) != len(subject):
|
|
833
|
-
return None
|
|
834
|
-
pattern_iterator = zip(node.patterns, subject)
|
|
835
|
-
|
|
836
|
-
# do iteration over patterns and values
|
|
837
|
-
bindings = {}
|
|
838
|
-
bound_names = set()
|
|
839
|
-
for pattern, item in pattern_iterator:
|
|
840
|
-
# recursive check
|
|
841
|
-
# noinspection DuplicatedCode
|
|
842
|
-
match = self._patma(pattern, item)
|
|
843
|
-
if match is None:
|
|
844
|
-
return None
|
|
845
|
-
|
|
846
|
-
# duplicate bindings check
|
|
847
|
-
if bound_names.intersection(match):
|
|
848
|
-
raise DraconicValueError(
|
|
849
|
-
f"multiple assignment to names {sorted(bound_names.intersection(match))} in sequence pattern",
|
|
850
|
-
node,
|
|
851
|
-
self._expr,
|
|
852
|
-
)
|
|
853
|
-
bindings.update(match)
|
|
854
|
-
bound_names.update(match)
|
|
855
|
-
|
|
856
|
-
return bindings
|
|
857
|
-
|
|
858
|
-
def _patma_match_mapping(self, node, subject):
|
|
859
|
-
if not isinstance(subject, Mapping):
|
|
860
|
-
return None
|
|
861
|
-
|
|
862
|
-
bindings = {}
|
|
863
|
-
bound_names = set()
|
|
864
|
-
bound_keys = set()
|
|
865
|
-
for key, pattern in zip(node.keys, node.patterns):
|
|
866
|
-
# recursive check
|
|
867
|
-
key = self._eval(key)
|
|
868
|
-
try:
|
|
869
|
-
value = subject[key]
|
|
870
|
-
except KeyError:
|
|
871
|
-
return None
|
|
872
|
-
# noinspection DuplicatedCode
|
|
873
|
-
match = self._patma(pattern, value)
|
|
874
|
-
if match is None:
|
|
875
|
-
return None
|
|
876
|
-
|
|
877
|
-
# duplicate bindings check
|
|
878
|
-
if bound_names.intersection(match):
|
|
879
|
-
raise DraconicValueError(
|
|
880
|
-
f"multiple assignment to names {sorted(bound_names.intersection(match))} in mapping pattern",
|
|
881
|
-
node,
|
|
882
|
-
self._expr,
|
|
883
|
-
)
|
|
884
|
-
bindings.update(match)
|
|
885
|
-
bound_names.update(match)
|
|
886
|
-
bound_keys.add(key)
|
|
887
|
-
|
|
888
|
-
if node.rest is not None:
|
|
889
|
-
if node.rest in bound_names:
|
|
890
|
-
raise DraconicValueError(
|
|
891
|
-
f"multiple assignment to name {node.rest!r} in mapping pattern", node, self._expr
|
|
892
|
-
)
|
|
893
|
-
bindings[node.rest] = {k: v for k, v in subject.items() if k not in bound_keys}
|
|
894
|
-
|
|
895
|
-
return bindings
|
|
896
|
-
|
|
897
|
-
@staticmethod
|
|
898
|
-
def _patma_match_star(node, subject):
|
|
899
|
-
if node.name is None:
|
|
900
|
-
return {}
|
|
901
|
-
return {node.name: subject}
|
|
902
|
-
|
|
903
|
-
def _patma_match_as(self, node, subject):
|
|
904
|
-
if node.name is None: # this is the wildcard pattern, always matches
|
|
905
|
-
return {}
|
|
906
|
-
if node.pattern is None: # bare name capture pattern, always matches
|
|
907
|
-
return {node.name: subject}
|
|
908
|
-
# otherwise if the inner match matches, we just add an additional binding to it
|
|
909
|
-
inner_match = self._patma(node.pattern, subject)
|
|
910
|
-
if inner_match is None:
|
|
911
|
-
return None
|
|
912
|
-
return {**inner_match, node.name: subject}
|
|
913
|
-
|
|
914
|
-
def _patma_match_or(self, node, subject):
|
|
915
|
-
# since we don't know the subpattern's bindings until it executes, we can't enforce both sides having the
|
|
916
|
-
# same bindings like in Python
|
|
917
|
-
for pattern in node.patterns:
|
|
918
|
-
match = self._patma(pattern, subject)
|
|
919
|
-
if match is not None:
|
|
920
|
-
return match
|
|
921
|
-
return None
|
|
922
|
-
|
|
923
|
-
# ===== functions =====
|
|
924
|
-
# definitions
|
|
925
|
-
def _eval_functiondef(self, node):
|
|
926
|
-
if node.name in self.builtins:
|
|
927
|
-
raise DraconicValueError(f"{node.name} is already builtin (no shadow assignments).", node, self._expr)
|
|
928
|
-
self._names[node.name] = _Function(self, node, self._names, self._expr)
|
|
929
|
-
|
|
930
|
-
def _eval_lambda(self, node):
|
|
931
|
-
return _Lambda(self, node, self._names, self._expr)
|
|
932
|
-
|
|
933
|
-
# executions
|
|
934
|
-
def _eval_call(self, node):
|
|
935
|
-
func = self._eval(node.func)
|
|
936
|
-
args = tuple(self._starred_unwrap(node.args, check_len=False))
|
|
937
|
-
kwargs = dict(self._starred_keyword_unwrap(((k.arg, k.value) for k in node.keywords), check_len=False))
|
|
938
|
-
try:
|
|
939
|
-
return func(*args, **kwargs)
|
|
940
|
-
except DraconicException as e:
|
|
941
|
-
raise NestedException(e.msg, node, self._expr, last_exc=e) from e
|
|
942
|
-
|
|
943
|
-
# noinspection PyProtectedMember
|
|
944
|
-
@contextlib.contextmanager
|
|
945
|
-
def _function_call_context(self, __functiondef, /, *args, **kwargs):
|
|
946
|
-
# check limits
|
|
947
|
-
self._depth += 1
|
|
948
|
-
if self._depth > self._config.max_recursion_depth:
|
|
949
|
-
_raise_in_context(TooMuchRecursion, "Maximum recursion depth exceeded")
|
|
950
|
-
# store current names and expression
|
|
951
|
-
old_names = self._names
|
|
952
|
-
old_expr = self._expr
|
|
953
|
-
# bind closure names and contextual expression
|
|
954
|
-
self._names = __functiondef._outer_scope_names.copy()
|
|
955
|
-
self._expr = __functiondef._defining_expr
|
|
956
|
-
|
|
957
|
-
# yield control to the call
|
|
958
|
-
try:
|
|
959
|
-
self._bind_function_args(__functiondef, *args, **kwargs)
|
|
960
|
-
yield
|
|
961
|
-
finally:
|
|
962
|
-
# restore old names and expr
|
|
963
|
-
self._names = old_names
|
|
964
|
-
self._expr = old_expr
|
|
965
|
-
# reduce recursion depth
|
|
966
|
-
self._depth -= 1
|
|
967
|
-
|
|
968
|
-
# noinspection PyProtectedMember
|
|
969
|
-
def _bind_function_args(self, __functiondef, /, *args, **kwargs):
|
|
970
|
-
# check and bind args
|
|
971
|
-
arguments = __functiondef._node.args
|
|
972
|
-
# check valid pos num
|
|
973
|
-
if len(args) > (numpos := len(arguments.posonlyargs) + len(arguments.args)) and arguments.vararg is None:
|
|
974
|
-
raise TypeError(f"{__functiondef._name}() takes {numpos} positional arguments but {len(args)} were given")
|
|
975
|
-
args_i = 0
|
|
976
|
-
default_i = len(arguments.defaults) - numpos
|
|
977
|
-
# posonly
|
|
978
|
-
for posonly in arguments.posonlyargs:
|
|
979
|
-
if args_i + 1 > len(args):
|
|
980
|
-
if default_i < 0:
|
|
981
|
-
raise TypeError(f"{__functiondef._name}() missing required positional argument: {posonly.arg!r}")
|
|
982
|
-
self._names[posonly.arg] = self._eval(arguments.defaults[default_i])
|
|
983
|
-
else:
|
|
984
|
-
self._names[posonly.arg] = args[args_i]
|
|
985
|
-
args_i += 1
|
|
986
|
-
default_i += 1
|
|
987
|
-
# normal
|
|
988
|
-
for posarg in arguments.args:
|
|
989
|
-
# at least 1
|
|
990
|
-
if args_i + 1 > len(args) and posarg.arg not in kwargs:
|
|
991
|
-
if default_i < 0:
|
|
992
|
-
raise TypeError(f"{__functiondef._name}() missing required positional argument: {posarg.arg!r}")
|
|
993
|
-
self._names[posarg.arg] = self._eval(arguments.defaults[default_i])
|
|
994
|
-
# pos and kw
|
|
995
|
-
elif args_i + 1 <= len(args) and posarg.arg in kwargs:
|
|
996
|
-
raise TypeError(f"{__functiondef._name}() got multiple values for argument {posarg.arg!r}")
|
|
997
|
-
elif posarg.arg in kwargs:
|
|
998
|
-
self._names[posarg.arg] = kwargs.pop(posarg.arg)
|
|
999
|
-
else:
|
|
1000
|
-
# we won't indexerror because if it's not in kwargs and args_i is invalid, the first if catches it
|
|
1001
|
-
self._names[posarg.arg] = args[args_i]
|
|
1002
|
-
args_i += 1
|
|
1003
|
-
default_i += 1
|
|
1004
|
-
# kwargonly
|
|
1005
|
-
for k_i, kwargonly in enumerate(arguments.kwonlyargs):
|
|
1006
|
-
if kwargonly.arg not in kwargs and arguments.kw_defaults[k_i] is None:
|
|
1007
|
-
raise TypeError(f"{__functiondef._name}() missing required keyword argument: {kwargonly.arg!r}")
|
|
1008
|
-
if kwargonly.arg in kwargs:
|
|
1009
|
-
self._names[kwargonly.arg] = kwargs.pop(kwargonly.arg)
|
|
1010
|
-
else:
|
|
1011
|
-
self._names[kwargonly.arg] = self._eval(arguments.kw_defaults[k_i])
|
|
1012
|
-
# *args
|
|
1013
|
-
if arguments.vararg is not None:
|
|
1014
|
-
if approx_len_of(args[args_i:]) > self._config.max_const_len:
|
|
1015
|
-
_raise_in_context(IterableTooLong, f"*{arguments.vararg.arg} would be too large")
|
|
1016
|
-
self._names[arguments.vararg.arg] = tuple(args[args_i:])
|
|
1017
|
-
# **kwargs
|
|
1018
|
-
if arguments.kwarg is not None:
|
|
1019
|
-
if approx_len_of(kwargs) > self._config.max_const_len:
|
|
1020
|
-
_raise_in_context(IterableTooLong, f"**{arguments.kwarg.arg} would be too large")
|
|
1021
|
-
self._names[arguments.kwarg.arg] = kwargs
|
|
1022
|
-
elif kwargs: # and arguments.kwarg is None (implicit)
|
|
1023
|
-
raise TypeError(f"{__functiondef._name}() got unexpected keyword arguments: {tuple(kwargs.keys())}")
|
|
1024
|
-
|
|
1025
|
-
# noinspection PyProtectedMember
|
|
1026
|
-
def _exec_function(self, __functiondef: _Function, /, *args, **kwargs):
|
|
1027
|
-
with self._function_call_context(__functiondef, *args, **kwargs):
|
|
1028
|
-
retval = self._exec(__functiondef._node.body)
|
|
1029
|
-
if isinstance(retval, (_Break, _Continue)):
|
|
1030
|
-
raise DraconicSyntaxError.from_node(retval.node, msg="Loop control outside loop", expr=self._expr)
|
|
1031
|
-
if isinstance(retval, _Return):
|
|
1032
|
-
return retval.value
|
|
1033
|
-
|
|
1034
|
-
# noinspection PyProtectedMember
|
|
1035
|
-
def _exec_lambda(self, __lambdadef: _Lambda, /, *args, **kwargs):
|
|
1036
|
-
with self._function_call_context(__lambdadef, *args, **kwargs):
|
|
1037
|
-
return self._eval(__lambdadef._node.body)
|
|
1038
|
-
|
|
1039
|
-
# ===== try/except =====
|
|
1040
|
-
def _exec_try(self, node: ast.Try):
|
|
1041
|
-
try:
|
|
1042
|
-
retval = self._exec(node.body)
|
|
1043
|
-
if isinstance(retval, (_Return, _Continue, _Break)):
|
|
1044
|
-
return retval
|
|
1045
|
-
except Exception as exc:
|
|
1046
|
-
if isinstance(exc, WrappedException):
|
|
1047
|
-
exc = exc.original
|
|
1048
|
-
# draconic diff: limit errors cannot be caught
|
|
1049
|
-
if isinstance(exc, LimitException):
|
|
1050
|
-
raise
|
|
1051
|
-
# enter into the except handlers
|
|
1052
|
-
for handler in node.handlers:
|
|
1053
|
-
if self._except_handler_matches(handler, exc):
|
|
1054
|
-
retval = self._except_handler(handler)
|
|
1055
|
-
if isinstance(retval, (_Return, _Continue, _Break)):
|
|
1056
|
-
return retval
|
|
1057
|
-
break
|
|
1058
|
-
else:
|
|
1059
|
-
raise
|
|
1060
|
-
else:
|
|
1061
|
-
retval = self._exec(node.orelse)
|
|
1062
|
-
if isinstance(retval, (_Return, _Continue, _Break)):
|
|
1063
|
-
return retval
|
|
1064
|
-
finally:
|
|
1065
|
-
retval = self._exec(node.finalbody)
|
|
1066
|
-
if isinstance(retval, (_Return, _Continue, _Break)):
|
|
1067
|
-
return retval
|
|
1068
|
-
|
|
1069
|
-
def _except_handler_matches(self, node: ast.ExceptHandler, exc: BaseException) -> bool:
|
|
1070
|
-
# draconic diff: exception handlers must be string literals, tuple[str] literals, or bare
|
|
1071
|
-
if node.type is None:
|
|
1072
|
-
return True
|
|
1073
|
-
if isinstance(node.type, ast.Str):
|
|
1074
|
-
return type(exc).__name__ == self._eval_str(node.type)
|
|
1075
|
-
elif isinstance(node.type, ast.Tuple):
|
|
1076
|
-
if not all(isinstance(item, ast.Str) for item in node.type.elts):
|
|
1077
|
-
raise FeatureNotAvailable(
|
|
1078
|
-
"'except' clause expressions must be string literals or tuple of string literals", node, self._expr
|
|
1079
|
-
)
|
|
1080
|
-
return type(exc).__name__ in self._eval_tuple(node.type)
|
|
1081
|
-
else:
|
|
1082
|
-
raise FeatureNotAvailable(
|
|
1083
|
-
"'except' clause expressions must be string literals or tuple of string literals", node, self._expr
|
|
1084
|
-
)
|
|
1085
|
-
|
|
1086
|
-
def _except_handler(self, node: ast.ExceptHandler):
|
|
1087
|
-
# draconic diff: 'as X' clause not allowed
|
|
1088
|
-
if node.name is not None:
|
|
1089
|
-
raise FeatureNotAvailable("'except ... as X' is not available in this interpreter", node, self._expr)
|
|
1090
|
-
# run body
|
|
1091
|
-
return self._exec(node.body)
|