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
avrae_ls/symbols.py
DELETED
|
@@ -1,266 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import ast
|
|
4
|
-
import logging
|
|
5
|
-
from dataclasses import dataclass
|
|
6
|
-
from typing import Dict, Iterable, List, Optional
|
|
7
|
-
|
|
8
|
-
import draconic
|
|
9
|
-
from lsprotocol import types
|
|
10
|
-
|
|
11
|
-
from .argument_parsing import apply_argument_parsing
|
|
12
|
-
from .parser import find_draconic_blocks
|
|
13
|
-
|
|
14
|
-
log = logging.getLogger(__name__)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
@dataclass
|
|
18
|
-
class SymbolEntry:
|
|
19
|
-
name: str
|
|
20
|
-
kind: types.SymbolKind
|
|
21
|
-
range: types.Range
|
|
22
|
-
selection_range: types.Range
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
class SymbolTable:
|
|
26
|
-
def __init__(self, entries: List[SymbolEntry]):
|
|
27
|
-
self._entries = entries
|
|
28
|
-
self._index: Dict[str, SymbolEntry] = {entry.name: entry for entry in entries}
|
|
29
|
-
|
|
30
|
-
@property
|
|
31
|
-
def entries(self) -> List[SymbolEntry]:
|
|
32
|
-
return self._entries
|
|
33
|
-
|
|
34
|
-
def lookup(self, name: str) -> Optional[SymbolEntry]:
|
|
35
|
-
return self._index.get(name)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def build_symbol_table(source: str) -> SymbolTable:
|
|
39
|
-
entries: list[SymbolEntry] = []
|
|
40
|
-
parsed_source = apply_argument_parsing(source)
|
|
41
|
-
blocks = find_draconic_blocks(parsed_source)
|
|
42
|
-
if not blocks:
|
|
43
|
-
entries.extend(_symbols_from_code(parsed_source, 0, 0))
|
|
44
|
-
else:
|
|
45
|
-
for block in blocks:
|
|
46
|
-
entries.extend(_symbols_from_code(block.code, block.line_offset, block.char_offset))
|
|
47
|
-
return SymbolTable(entries)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def document_symbols(source: str) -> List[types.DocumentSymbol]:
|
|
51
|
-
table = build_symbol_table(source)
|
|
52
|
-
return [
|
|
53
|
-
types.DocumentSymbol(
|
|
54
|
-
name=entry.name,
|
|
55
|
-
kind=entry.kind,
|
|
56
|
-
range=entry.range,
|
|
57
|
-
selection_range=entry.selection_range,
|
|
58
|
-
)
|
|
59
|
-
for entry in table.entries
|
|
60
|
-
]
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
def find_definition_range(table: SymbolTable, name: str) -> types.Range | None:
|
|
64
|
-
entry = table.lookup(name)
|
|
65
|
-
if entry:
|
|
66
|
-
return entry.selection_range
|
|
67
|
-
return None
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
def find_references(
|
|
71
|
-
table: SymbolTable, source: str, name: str, include_declaration: bool = True
|
|
72
|
-
) -> List[types.Range]:
|
|
73
|
-
parsed_source = apply_argument_parsing(source)
|
|
74
|
-
ranges: list[types.Range] = []
|
|
75
|
-
entry = table.lookup(name)
|
|
76
|
-
include_stores = include_declaration and entry is None
|
|
77
|
-
if include_declaration:
|
|
78
|
-
if entry:
|
|
79
|
-
ranges.append(entry.selection_range)
|
|
80
|
-
|
|
81
|
-
blocks = find_draconic_blocks(parsed_source)
|
|
82
|
-
if not blocks:
|
|
83
|
-
ranges.extend(_references_from_code(parsed_source, name, 0, 0, include_stores))
|
|
84
|
-
else:
|
|
85
|
-
for block in blocks:
|
|
86
|
-
ranges.extend(
|
|
87
|
-
_references_from_code(block.code, name, block.line_offset, block.char_offset, include_stores)
|
|
88
|
-
)
|
|
89
|
-
return _dedupe_ranges(ranges)
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
def range_for_word(source: str, position: types.Position) -> types.Range | None:
|
|
93
|
-
lines = source.splitlines()
|
|
94
|
-
if position.line >= len(lines):
|
|
95
|
-
return None
|
|
96
|
-
line = lines[position.line]
|
|
97
|
-
if position.character > len(line):
|
|
98
|
-
return None
|
|
99
|
-
|
|
100
|
-
def _is_ident(ch: str) -> bool:
|
|
101
|
-
return ch.isalnum() or ch == "_"
|
|
102
|
-
|
|
103
|
-
start_idx = position.character
|
|
104
|
-
while start_idx > 0 and _is_ident(line[start_idx - 1]):
|
|
105
|
-
start_idx -= 1
|
|
106
|
-
|
|
107
|
-
end_idx = position.character
|
|
108
|
-
while end_idx < len(line) and _is_ident(line[end_idx]):
|
|
109
|
-
end_idx += 1
|
|
110
|
-
|
|
111
|
-
if start_idx == end_idx:
|
|
112
|
-
return None
|
|
113
|
-
|
|
114
|
-
return types.Range(
|
|
115
|
-
start=types.Position(line=position.line, character=start_idx),
|
|
116
|
-
end=types.Position(line=position.line, character=end_idx),
|
|
117
|
-
)
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
def _symbols_from_code(code: str, line_offset: int, char_offset: int) -> List[SymbolEntry]:
|
|
121
|
-
body, offset_adjust = _parse_draconic(code)
|
|
122
|
-
if not body:
|
|
123
|
-
return []
|
|
124
|
-
|
|
125
|
-
local_offset = line_offset + offset_adjust
|
|
126
|
-
|
|
127
|
-
entries: list[SymbolEntry] = []
|
|
128
|
-
for node in body:
|
|
129
|
-
entry = _entry_from_node(node, local_offset, char_offset)
|
|
130
|
-
if entry:
|
|
131
|
-
entries.append(entry)
|
|
132
|
-
return entries
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
def _entry_from_node(node: ast.AST, line_offset: int = 0, char_offset: int = 0) -> SymbolEntry | None:
|
|
136
|
-
if isinstance(node, ast.FunctionDef):
|
|
137
|
-
kind = types.SymbolKind.Function
|
|
138
|
-
name = node.name
|
|
139
|
-
elif isinstance(node, ast.ClassDef):
|
|
140
|
-
kind = types.SymbolKind.Class
|
|
141
|
-
name = node.name
|
|
142
|
-
elif isinstance(node, ast.Assign) and node.targets:
|
|
143
|
-
target = node.targets[0]
|
|
144
|
-
if isinstance(target, ast.Name):
|
|
145
|
-
kind = types.SymbolKind.Variable
|
|
146
|
-
name = target.id
|
|
147
|
-
node = target
|
|
148
|
-
else:
|
|
149
|
-
return None
|
|
150
|
-
else:
|
|
151
|
-
return None
|
|
152
|
-
|
|
153
|
-
rng = _range_from_positions(
|
|
154
|
-
getattr(node, "lineno", 1),
|
|
155
|
-
getattr(node, "col_offset", 0),
|
|
156
|
-
getattr(node, "end_lineno", None),
|
|
157
|
-
getattr(node, "end_col_offset", None),
|
|
158
|
-
)
|
|
159
|
-
rng = _shift_range(rng, line_offset, char_offset)
|
|
160
|
-
return SymbolEntry(name=name, kind=kind, range=rng, selection_range=rng)
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
class _ReferenceCollector(ast.NodeVisitor):
|
|
164
|
-
def __init__(self, target: str, include_stores: bool):
|
|
165
|
-
super().__init__()
|
|
166
|
-
self._target = target
|
|
167
|
-
self._include_stores = include_stores
|
|
168
|
-
self.ranges: list[types.Range] = []
|
|
169
|
-
|
|
170
|
-
def visit_Name(self, node: ast.Name): # type: ignore[override]
|
|
171
|
-
if node.id == self._target:
|
|
172
|
-
if isinstance(node.ctx, ast.Store) and not self._include_stores:
|
|
173
|
-
return
|
|
174
|
-
rng = _range_from_positions(
|
|
175
|
-
getattr(node, "lineno", 1),
|
|
176
|
-
getattr(node, "col_offset", 0),
|
|
177
|
-
getattr(node, "end_lineno", None),
|
|
178
|
-
getattr(node, "end_col_offset", None),
|
|
179
|
-
)
|
|
180
|
-
self.ranges.append(rng)
|
|
181
|
-
self.generic_visit(node)
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
def _references_from_code(
|
|
185
|
-
code: str,
|
|
186
|
-
name: str,
|
|
187
|
-
line_offset: int,
|
|
188
|
-
char_offset: int,
|
|
189
|
-
include_stores: bool,
|
|
190
|
-
) -> List[types.Range]:
|
|
191
|
-
body, offset_adjust = _parse_draconic(code)
|
|
192
|
-
if not body:
|
|
193
|
-
return []
|
|
194
|
-
|
|
195
|
-
collector = _ReferenceCollector(name, include_stores)
|
|
196
|
-
for node in body:
|
|
197
|
-
collector.visit(node)
|
|
198
|
-
|
|
199
|
-
local_offset = line_offset + offset_adjust
|
|
200
|
-
return [_shift_range(rng, local_offset, char_offset) for rng in collector.ranges]
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
def _parse_draconic(code: str) -> tuple[list[ast.AST], int]:
|
|
204
|
-
parser = draconic.DraconicInterpreter()
|
|
205
|
-
try:
|
|
206
|
-
return parser.parse(code), 0
|
|
207
|
-
except draconic.DraconicSyntaxError:
|
|
208
|
-
wrapped, added = _wrap_draconic(code)
|
|
209
|
-
try:
|
|
210
|
-
return parser.parse(wrapped), -added
|
|
211
|
-
except draconic.DraconicSyntaxError:
|
|
212
|
-
return [], 0
|
|
213
|
-
except Exception as exc: # pragma: no cover - defensive
|
|
214
|
-
log.debug("Symbol extraction failed: %s", exc)
|
|
215
|
-
return [], 0
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
def _range_from_positions(
|
|
219
|
-
lineno: int | None,
|
|
220
|
-
col_offset: int | None,
|
|
221
|
-
end_lineno: int | None,
|
|
222
|
-
end_col_offset: int | None,
|
|
223
|
-
) -> types.Range:
|
|
224
|
-
start_line = max((lineno or 1) - 1, 0)
|
|
225
|
-
start_char = max(col_offset or 0, 0)
|
|
226
|
-
end_line = max(((end_lineno or lineno or 1) - 1), 0)
|
|
227
|
-
raw_end_char = end_col_offset if end_col_offset is not None else col_offset
|
|
228
|
-
end_char = max(raw_end_char or start_char, start_char)
|
|
229
|
-
if end_char <= start_char:
|
|
230
|
-
end_char = start_char + 1
|
|
231
|
-
return types.Range(
|
|
232
|
-
start=types.Position(line=start_line, character=start_char),
|
|
233
|
-
end=types.Position(line=end_line, character=end_char),
|
|
234
|
-
)
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
def _shift_range(rng: types.Range, line_offset: int, char_offset: int = 0) -> types.Range:
|
|
238
|
-
start_char = rng.start.character + (char_offset if rng.start.line == 0 else 0)
|
|
239
|
-
end_char = rng.end.character + (char_offset if rng.end.line == 0 else 0)
|
|
240
|
-
if line_offset == 0:
|
|
241
|
-
return types.Range(
|
|
242
|
-
start=types.Position(line=rng.start.line, character=start_char),
|
|
243
|
-
end=types.Position(line=rng.end.line, character=end_char),
|
|
244
|
-
)
|
|
245
|
-
return types.Range(
|
|
246
|
-
start=types.Position(line=max(rng.start.line + line_offset, 0), character=max(start_char, 0)),
|
|
247
|
-
end=types.Position(line=max(rng.end.line + line_offset, 0), character=max(end_char, 0)),
|
|
248
|
-
)
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
def _wrap_draconic(code: str) -> tuple[str, int]:
|
|
252
|
-
indented = "\n".join(f" {line}" for line in code.splitlines())
|
|
253
|
-
wrapped = f"def __alias_main__():\n{indented}\n__alias_main__()"
|
|
254
|
-
return wrapped, 1
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
def _dedupe_ranges(ranges: Iterable[types.Range]) -> List[types.Range]:
|
|
258
|
-
seen = set()
|
|
259
|
-
unique: list[types.Range] = []
|
|
260
|
-
for rng in ranges:
|
|
261
|
-
key = (rng.start.line, rng.start.character, rng.end.line, rng.end.character)
|
|
262
|
-
if key in seen:
|
|
263
|
-
continue
|
|
264
|
-
seen.add(key)
|
|
265
|
-
unique.append(rng)
|
|
266
|
-
return unique
|
avrae_ls-0.4.1.dist-info/RECORD
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
avrae_ls/__init__.py,sha256=BmjrnksGkbG7TPqwbyQvgYj9uei8pFSFpfkRpaGVdJU,63
|
|
2
|
-
avrae_ls/__main__.py,sha256=ch287lWe11go5xHAE9OkVppixt0vRF401E4zTs2tqQ0,3557
|
|
3
|
-
avrae_ls/alias_preview.py,sha256=YFUiY36runNe6R0k-9YrbGff4l3evgEsoWbVc8WT5nU,11462
|
|
4
|
-
avrae_ls/api.py,sha256=_AHvIEIlz34YeWdZDpXd1EwgdQUGk8-nqNIEN4qdNR8,65093
|
|
5
|
-
avrae_ls/argparser.py,sha256=DRptXGyK4f0r7NsuV1Sg4apG-qihIClOX408jgk4HH4,13762
|
|
6
|
-
avrae_ls/argument_parsing.py,sha256=ezKl65VwuNEDxt6KlYwVQcpy1110UDvf4BqZqgZTcqk,2122
|
|
7
|
-
avrae_ls/code_actions.py,sha256=CYl3nMzCaE9k35p5fWAmzsTOVfcv2RrkA3SbtyCdcsI,9423
|
|
8
|
-
avrae_ls/codes.py,sha256=iPRPQ6i9DZheae4_ra1y29vCw3Y4SEu6Udf5WiZj_RY,136
|
|
9
|
-
avrae_ls/completions.py,sha256=CvwKp3f20RsPFPnjUv03xCbuaGKIyIF7VgVhYNbig4U,51180
|
|
10
|
-
avrae_ls/config.py,sha256=ULEfWbzIO05cllHBLUeGY_CHLpX6tHyCJYIEQgKSoXc,17383
|
|
11
|
-
avrae_ls/context.py,sha256=KcCKqzqJCG2xlEtou5HWz1OWISApGl8IfwOvVnNi9nc,8464
|
|
12
|
-
avrae_ls/cvars.py,sha256=0tcVbUHx_CKJ6aou3kEsKX37LRWAjkUWlqqIuSRFlXk,3197
|
|
13
|
-
avrae_ls/diagnostics.py,sha256=wq5sBjI11NyTNtnjoZTGm1vdcvOvW7OoPHbCDAbF308,24914
|
|
14
|
-
avrae_ls/dice.py,sha256=DY7V7L-EwAXaCgddgVe9xU1s9lVtiw5Zc2reipNgdTk,874
|
|
15
|
-
avrae_ls/parser.py,sha256=UQDwjupuJVU6KvoF4D8r3azPT7ksP_nTZsm5FUhGnWE,1395
|
|
16
|
-
avrae_ls/runtime.py,sha256=PDnjZXcU5DqM9jKSc4S0jHObBE7SWKte17PWWXGQ34M,23356
|
|
17
|
-
avrae_ls/server.py,sha256=s3Cl6Ta0954FqBUh9pM326TwdUBLb7ZuyzR8vjQqs5c,15101
|
|
18
|
-
avrae_ls/signature_help.py,sha256=sVQncCeLNCD3UBVPZu9ZFw0b-8wq64n3qFPLtWSy_Cs,8827
|
|
19
|
-
avrae_ls/symbols.py,sha256=Lm5LH-F60JYNuAtSjRhHeyHEWAWxpnNb0w6KR0u1Zac,8422
|
|
20
|
-
avrae_ls-0.4.1.dist-info/licenses/LICENSE,sha256=O-0zMbcEi6wXz1DiSdVgzMlQjJcNqNe5KDv08uYzqR0,1055
|
|
21
|
-
draconic/LICENSE,sha256=Fzvu32_DafLKKn2mzxhEdlmrKZzAsigDZ87O7uoVqZI,1067
|
|
22
|
-
draconic/__init__.py,sha256=YPH420Pcn_nTkfB62hJy_YqC5kpJdzSa78jP8n4z_xY,109
|
|
23
|
-
draconic/exceptions.py,sha256=siahnHIsumbaUhKBDSrw_DmLZ-0oZks8L5oytPH8hD4,3753
|
|
24
|
-
draconic/helpers.py,sha256=jY-f9KHWP0ey2q09g6QsbtVxxet8LoQoZ8mAfju1E5c,9470
|
|
25
|
-
draconic/interpreter.py,sha256=ksL7qHwXmXsvSqf685xFEnzVLng6AZ2ewk1z7ymbKtc,42004
|
|
26
|
-
draconic/string.py,sha256=kGrRc6wNHRq1y5xw8Os-fBhfINDtIY2nBWQWkyLSfQI,2858
|
|
27
|
-
draconic/types.py,sha256=1Lsr6z8bW5agglGI4hLt_nPtYuZOIf_ueSpPDB4WDrs,13686
|
|
28
|
-
draconic/utils.py,sha256=D4vJ-txqS2-rlqsEpXAC46_j1sZX4UjY-9zIgElo96k,3122
|
|
29
|
-
draconic/versions.py,sha256=CUEsgUWjAmjez0432WwiBwZlIzWPIObwZUf8Yld18EE,84
|
|
30
|
-
avrae_ls-0.4.1.dist-info/METADATA,sha256=4kw6QJtVMqp_8pHBPra7sJXjchuCQdSOYiwj_dt7j1k,4713
|
|
31
|
-
avrae_ls-0.4.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
32
|
-
avrae_ls-0.4.1.dist-info/entry_points.txt,sha256=OtYXipMQzqmxpMoApgo0MeJYFmMbkbFN51Ibhpb8hF4,52
|
|
33
|
-
avrae_ls-0.4.1.dist-info/top_level.txt,sha256=TL68uzGHmB2R2ID32_s2zocmcNnpMJVQ6_4NBvyo8a4,18
|
|
34
|
-
avrae_ls-0.4.1.dist-info/RECORD,,
|
draconic/__init__.py
DELETED
draconic/exceptions.py
DELETED
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
import abc
|
|
2
|
-
import ast
|
|
3
|
-
|
|
4
|
-
from .versions import PY_310
|
|
5
|
-
|
|
6
|
-
__all__ = (
|
|
7
|
-
"DraconicException",
|
|
8
|
-
"DraconicSyntaxError",
|
|
9
|
-
"InvalidExpression",
|
|
10
|
-
"NotDefined",
|
|
11
|
-
"FeatureNotAvailable",
|
|
12
|
-
"DraconicValueError",
|
|
13
|
-
"LimitException",
|
|
14
|
-
"NumberTooHigh",
|
|
15
|
-
"IterableTooLong",
|
|
16
|
-
"TooManyStatements",
|
|
17
|
-
"TooMuchRecursion",
|
|
18
|
-
"WrappedException",
|
|
19
|
-
"AnnotatedException",
|
|
20
|
-
"NestedException",
|
|
21
|
-
"_PostponedRaise",
|
|
22
|
-
"_raise_in_context",
|
|
23
|
-
)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
class DraconicException(Exception):
|
|
27
|
-
"""Base exception for all exceptions in this library."""
|
|
28
|
-
|
|
29
|
-
__drac_context__: str = None
|
|
30
|
-
|
|
31
|
-
def __init__(self, msg):
|
|
32
|
-
super().__init__(msg)
|
|
33
|
-
self.msg = msg
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
class DraconicSyntaxError(DraconicException):
|
|
37
|
-
"""Bad syntax."""
|
|
38
|
-
|
|
39
|
-
def __init__(self, original: SyntaxError, expr):
|
|
40
|
-
super().__init__(original.msg)
|
|
41
|
-
self.lineno = original.lineno
|
|
42
|
-
self.offset = original.offset
|
|
43
|
-
self.end_lineno = None
|
|
44
|
-
self.end_offset = None
|
|
45
|
-
self.expr = expr
|
|
46
|
-
|
|
47
|
-
if PY_310:
|
|
48
|
-
self.end_lineno = original.end_lineno
|
|
49
|
-
self.end_offset = original.end_offset
|
|
50
|
-
|
|
51
|
-
@classmethod
|
|
52
|
-
def from_node(cls, node: ast.AST, msg: str, expr):
|
|
53
|
-
if PY_310:
|
|
54
|
-
inner = SyntaxError(
|
|
55
|
-
msg, ("<string>", node.lineno, node.col_offset + 1, expr, node.end_lineno, node.end_col_offset + 1)
|
|
56
|
-
)
|
|
57
|
-
else:
|
|
58
|
-
inner = SyntaxError(msg, ("<string>", node.lineno, node.col_offset + 1, expr))
|
|
59
|
-
return cls(inner, expr)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
class InvalidExpression(DraconicException):
|
|
63
|
-
"""Base exception for all exceptions during run-time."""
|
|
64
|
-
|
|
65
|
-
def __init__(self, msg, node, expr):
|
|
66
|
-
super().__init__(msg)
|
|
67
|
-
self.node = node
|
|
68
|
-
self.expr = expr
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
class NotDefined(InvalidExpression):
|
|
72
|
-
"""Some name is not defined."""
|
|
73
|
-
|
|
74
|
-
pass
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
class FeatureNotAvailable(InvalidExpression):
|
|
78
|
-
"""What you're trying to do is not allowed."""
|
|
79
|
-
|
|
80
|
-
pass
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
class DraconicValueError(InvalidExpression):
|
|
84
|
-
"""Bad value passed to some function."""
|
|
85
|
-
|
|
86
|
-
pass
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
class LimitException(InvalidExpression):
|
|
90
|
-
"""
|
|
91
|
-
Something exceeded execution limits.
|
|
92
|
-
"""
|
|
93
|
-
|
|
94
|
-
pass
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
class NumberTooHigh(LimitException):
|
|
98
|
-
"""Some number is way too big."""
|
|
99
|
-
|
|
100
|
-
pass
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
class IterableTooLong(LimitException):
|
|
104
|
-
"""Some iterable is way too big."""
|
|
105
|
-
|
|
106
|
-
pass
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
class TooManyStatements(LimitException):
|
|
110
|
-
"""Tried to execute too many statements."""
|
|
111
|
-
|
|
112
|
-
pass
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
class TooMuchRecursion(LimitException):
|
|
116
|
-
"""Too deep in recursion."""
|
|
117
|
-
|
|
118
|
-
pass
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
class WrappedException(InvalidExpression, abc.ABC):
|
|
122
|
-
"""abstract base exception for a lib exception that wraps some other exception"""
|
|
123
|
-
|
|
124
|
-
original: BaseException
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
class AnnotatedException(WrappedException):
|
|
128
|
-
"""A wrapper for another exception to handle lineno info."""
|
|
129
|
-
|
|
130
|
-
def __init__(self, original, node, expr):
|
|
131
|
-
super().__init__(str(original), node, expr)
|
|
132
|
-
self.original = original
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
class NestedException(WrappedException):
|
|
136
|
-
"""An exception occurred in a user-defined function call."""
|
|
137
|
-
|
|
138
|
-
def __init__(self, msg, node, expr, last_exc):
|
|
139
|
-
super().__init__(msg, node, expr)
|
|
140
|
-
self.last_exc = last_exc # type: DraconicException # used for tracebacking
|
|
141
|
-
# keep a reference to the end of the chain for easy comparison
|
|
142
|
-
if isinstance(last_exc, WrappedException):
|
|
143
|
-
self.original = last_exc.original
|
|
144
|
-
else:
|
|
145
|
-
self.original = last_exc
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
# we need to raise some exception, but don't have the node context right now
|
|
149
|
-
class _PostponedRaise(Exception):
|
|
150
|
-
def __init__(self, cls, *args, **kwargs):
|
|
151
|
-
self.cls = cls
|
|
152
|
-
self.args = args
|
|
153
|
-
self.kwargs = kwargs
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
def _raise_in_context(cls, *args, **kwargs):
|
|
157
|
-
raise _PostponedRaise(cls, *args, **kwargs)
|
draconic/helpers.py
DELETED
|
@@ -1,236 +0,0 @@
|
|
|
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 :])
|