avrae-ls 0.6.0__py3-none-any.whl → 0.6.2__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.
draconic/helpers.py ADDED
@@ -0,0 +1,236 @@
1
+ import ast
2
+ import operator as op
3
+ from typing import Sequence
4
+
5
+ from .exceptions import *
6
+ from .types import *
7
+
8
+ __all__ = ("DraconicConfig", "OperatorMixin", "zip_star")
9
+
10
+ # ===== config =====
11
+ DISALLOW_PREFIXES = ["_", "func_"]
12
+ DISALLOW_METHODS = ["format", "format_map", "mro", "tb_frame", "gi_frame", "ag_frame", "cr_frame", "exec"]
13
+
14
+
15
+ class DraconicConfig:
16
+ """A configuration object to pass into the Draconic interpreter."""
17
+
18
+ def __init__(
19
+ self,
20
+ max_const_len=200_000,
21
+ max_loops=10_000,
22
+ max_statements=100_000,
23
+ max_power_base=1_000_000,
24
+ max_power=1_000,
25
+ disallow_prefixes=None,
26
+ disallow_methods=None,
27
+ default_names=None,
28
+ builtins_extend_default=True,
29
+ max_int_size=64,
30
+ max_recursion_depth=50,
31
+ ):
32
+ """
33
+ Configuration object for the Draconic interpreter.
34
+
35
+ :param int max_const_len: The maximum length literal that should be allowed to be constructed.
36
+ :param int max_loops: The maximum total number of loops allowed per execution.
37
+ :param int max_statements: The maximum total number of statements allowed per execution.
38
+ :param int max_power_base: The maximum power base (x in x ** y)
39
+ :param int max_power: The maximum power (y in x ** y)
40
+ :param list disallow_prefixes: A list of str - attributes starting with any of these will be inaccessible
41
+ :param list disallow_methods: A list of str - methods named these will not be callable
42
+ :param dict default_names: A dict of str: Any - default names in the runtime
43
+ :param bool builtins_extend_default: If False, ``builtins`` to the interpreter overrides default names
44
+ :param int max_int_size: The maximum allowed size of integers (-2^(pow-1) to 2^(pow-1)-1). Default 64.
45
+ Integers can technically reach up to double this size before size check.
46
+ *Not* the max value!
47
+ :param int max_recursion_depth: The maximum allowed recursion depth.
48
+ """
49
+ if disallow_prefixes is None:
50
+ disallow_prefixes = DISALLOW_PREFIXES
51
+ if disallow_methods is None:
52
+ disallow_methods = DISALLOW_METHODS
53
+
54
+ self.max_const_len = max_const_len
55
+ self.max_loops = max_loops
56
+ self.max_statements = max_statements
57
+ self.max_power_base = max_power_base
58
+ self.max_power = max_power
59
+ self.max_int_size = max_int_size
60
+ self.min_int = -(2 ** (max_int_size - 1))
61
+ self.max_int = (2 ** (max_int_size - 1)) - 1
62
+ self.disallow_prefixes = disallow_prefixes
63
+ self.disallow_methods = disallow_methods
64
+ self.builtins_extend_default = builtins_extend_default
65
+ self.max_recursion_depth = max_recursion_depth
66
+
67
+ # types
68
+ self._list = safe_list(self)
69
+ self._dict = safe_dict(self)
70
+ self._set = safe_set(self)
71
+ self._str = safe_str(self)
72
+
73
+ # default names
74
+ if default_names is None:
75
+ default_names = self._default_names()
76
+ self.default_names = default_names
77
+
78
+ @property
79
+ def list(self):
80
+ return self._list
81
+
82
+ @property
83
+ def dict(self):
84
+ return self._dict
85
+
86
+ @property
87
+ def set(self):
88
+ return self._set
89
+
90
+ @property
91
+ def str(self):
92
+ return self._str
93
+
94
+ def _default_names(self):
95
+ return {
96
+ "True": True,
97
+ "False": False,
98
+ "None": None,
99
+ # functions
100
+ "bool": bool,
101
+ "int": int,
102
+ "float": float,
103
+ "str": self.str,
104
+ "tuple": tuple,
105
+ "dict": self.dict,
106
+ "list": self.list,
107
+ "set": self.set,
108
+ }
109
+
110
+
111
+ # ===== operators =====
112
+ class OperatorMixin:
113
+ """A mixin class to provide the operators."""
114
+
115
+ def __init__(self, config):
116
+ """
117
+ :type config: draconic.helpers.DraconicConfig
118
+ """
119
+ self._config = config
120
+
121
+ self.operators = {
122
+ # binary
123
+ ast.Add: self._safe_add,
124
+ ast.Sub: self._safe_sub,
125
+ ast.Mult: self._safe_mult,
126
+ ast.Div: op.truediv,
127
+ ast.FloorDiv: op.floordiv,
128
+ ast.Pow: self._safe_power,
129
+ ast.Mod: op.mod,
130
+ ast.LShift: self._safe_lshift,
131
+ ast.RShift: op.rshift,
132
+ ast.BitOr: op.or_,
133
+ ast.BitXor: op.xor,
134
+ ast.BitAnd: op.and_,
135
+ ast.Invert: op.invert,
136
+ # unary
137
+ ast.Not: op.not_,
138
+ ast.USub: op.neg,
139
+ ast.UAdd: op.pos,
140
+ # comparison
141
+ ast.Eq: op.eq,
142
+ ast.NotEq: op.ne,
143
+ ast.Gt: op.gt,
144
+ ast.Lt: op.lt,
145
+ ast.GtE: op.ge,
146
+ ast.LtE: op.le,
147
+ ast.In: lambda x, y: op.contains(y, x),
148
+ ast.NotIn: lambda x, y: not op.contains(y, x),
149
+ ast.Is: lambda x, y: x is y,
150
+ ast.IsNot: lambda x, y: x is not y,
151
+ }
152
+
153
+ def _safe_power(self, a, b):
154
+ """Exponent: limit power base and power to prevent CPU-locking computation"""
155
+ if abs(a) > self._config.max_power_base or abs(b) > self._config.max_power:
156
+ _raise_in_context(NumberTooHigh, f"{a} ** {b} is too large of an exponent")
157
+ result = a**b
158
+ if isinstance(result, int) and (result < self._config.min_int or result > self._config.max_int):
159
+ _raise_in_context(NumberTooHigh, "This exponent would create a number too large")
160
+ return result
161
+
162
+ def _safe_mult(self, a, b):
163
+ """Multiplication: limit the size of iterables that can be created, and the max size of ints"""
164
+ # sequences can only be multiplied by ints, so this is safe
165
+ self._check_binop_operands(a, b)
166
+ if isinstance(b, int) and b * approx_len_of(a) > self._config.max_const_len:
167
+ _raise_in_context(IterableTooLong, "Multiplying these two would create something too long")
168
+ if isinstance(a, int) and a * approx_len_of(b) > self._config.max_const_len:
169
+ _raise_in_context(IterableTooLong, "Multiplying these two would create something too long")
170
+ result = a * b
171
+ if isinstance(result, int) and (result < self._config.min_int or result > self._config.max_int):
172
+ _raise_in_context(NumberTooHigh, "Multiplying these two would create a number too large")
173
+ return result
174
+
175
+ def _safe_add(self, a, b):
176
+ """Addition: limit the size of iterables that can be created, and the max size of ints"""
177
+ self._check_binop_operands(a, b)
178
+ if approx_len_of(a) + approx_len_of(b) > self._config.max_const_len:
179
+ _raise_in_context(IterableTooLong, "Adding these two would create something too long")
180
+ result = a + b
181
+ if isinstance(result, int) and (result < self._config.min_int or result > self._config.max_int):
182
+ _raise_in_context(NumberTooHigh, "Adding these two would create a number too large")
183
+ return result
184
+
185
+ def _safe_sub(self, a, b):
186
+ """Addition: limit the max size of ints"""
187
+ self._check_binop_operands(a, b)
188
+ result = a - b
189
+ if isinstance(result, int) and (result < self._config.min_int or result > self._config.max_int):
190
+ _raise_in_context(NumberTooHigh, "Subtracting these two would create a number too large")
191
+ return result
192
+
193
+ def _safe_lshift(self, a, b):
194
+ """Left Bit-Shift: limit the size of integers/floats to prevent CPU-locking computation"""
195
+ self._check_binop_operands(a, b)
196
+
197
+ if isinstance(b, int) and b > self._config.max_int_size - 2:
198
+ _raise_in_context(NumberTooHigh, f"{a} << {b} is too large of a shift")
199
+
200
+ result = a << b
201
+ if isinstance(result, int) and (result < self._config.min_int or result > self._config.max_int):
202
+ _raise_in_context(NumberTooHigh, "Shifting these two would create a number too large")
203
+
204
+ return a << b
205
+
206
+ def _check_binop_operands(self, a, b):
207
+ """Ensures both operands of a binary operation are safe (int limit)."""
208
+ if isinstance(a, int) and (a < self._config.min_int or a > self._config.max_int):
209
+ _raise_in_context(NumberTooHigh, "This number is too large")
210
+ if isinstance(b, int) and (b < self._config.min_int or b > self._config.max_int):
211
+ _raise_in_context(NumberTooHigh, "This number is too large")
212
+
213
+
214
+ # ==== other utils ====
215
+ def zip_star(a: Sequence, b: Sequence, star_index: int):
216
+ """
217
+ Like zip(a, b), but zips the element at ``a[star_index]`` with a list of 0..len(b) elements such that every other
218
+ element of ``a`` maps to exactly one element of ``b``.
219
+
220
+ >>> zip_star(['a', 'b', 'c'], [1, 2, 3, 4], star_index=1) # like a, *b, c = [1, 2, 3, 4]
221
+ [('a', 1), ('b', [2, 3]), ('c', 4)]
222
+ >>> zip_star(['a', 'b', 'c'], [1, 2], star_index=1) # like a, *b, c = [1, 2]
223
+ [('a', 1), ('b', []), ('c', 2)]
224
+
225
+ Requires ``len(b) >= len(a) - 1`` and ``star_index < len(a)``.
226
+ """
227
+ if not 0 <= star_index < len(a):
228
+ raise IndexError("'star_index' must be a valid index of 'a'")
229
+ if not len(b) >= len(a) - 1:
230
+ raise ValueError("'b' must be no more than 1 shorter than 'a'")
231
+
232
+ length_difference = len(b) - (len(a) - 1)
233
+
234
+ yield from zip(a[:star_index], b[:star_index])
235
+ yield a[star_index], b[star_index : star_index + length_difference]
236
+ yield from zip(a[star_index + 1 :], b[star_index + length_difference :])