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.
avrae_ls/symbols.py ADDED
@@ -0,0 +1,274 @@
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
+ elif isinstance(node, ast.AnnAssign):
151
+ target = node.target
152
+ if isinstance(target, ast.Name):
153
+ kind = types.SymbolKind.Variable
154
+ name = target.id
155
+ node = target
156
+ else:
157
+ return None
158
+ else:
159
+ return None
160
+
161
+ rng = _range_from_positions(
162
+ getattr(node, "lineno", 1),
163
+ getattr(node, "col_offset", 0),
164
+ getattr(node, "end_lineno", None),
165
+ getattr(node, "end_col_offset", None),
166
+ )
167
+ rng = _shift_range(rng, line_offset, char_offset)
168
+ return SymbolEntry(name=name, kind=kind, range=rng, selection_range=rng)
169
+
170
+
171
+ class _ReferenceCollector(ast.NodeVisitor):
172
+ def __init__(self, target: str, include_stores: bool):
173
+ super().__init__()
174
+ self._target = target
175
+ self._include_stores = include_stores
176
+ self.ranges: list[types.Range] = []
177
+
178
+ def visit_Name(self, node: ast.Name): # type: ignore[override]
179
+ if node.id == self._target:
180
+ if isinstance(node.ctx, ast.Store) and not self._include_stores:
181
+ return
182
+ rng = _range_from_positions(
183
+ getattr(node, "lineno", 1),
184
+ getattr(node, "col_offset", 0),
185
+ getattr(node, "end_lineno", None),
186
+ getattr(node, "end_col_offset", None),
187
+ )
188
+ self.ranges.append(rng)
189
+ self.generic_visit(node)
190
+
191
+
192
+ def _references_from_code(
193
+ code: str,
194
+ name: str,
195
+ line_offset: int,
196
+ char_offset: int,
197
+ include_stores: bool,
198
+ ) -> List[types.Range]:
199
+ body, offset_adjust = _parse_draconic(code)
200
+ if not body:
201
+ return []
202
+
203
+ collector = _ReferenceCollector(name, include_stores)
204
+ for node in body:
205
+ collector.visit(node)
206
+
207
+ local_offset = line_offset + offset_adjust
208
+ return [_shift_range(rng, local_offset, char_offset) for rng in collector.ranges]
209
+
210
+
211
+ def _parse_draconic(code: str) -> tuple[list[ast.AST], int]:
212
+ parser = draconic.DraconicInterpreter()
213
+ try:
214
+ return parser.parse(code), 0
215
+ except draconic.DraconicSyntaxError:
216
+ wrapped, added = _wrap_draconic(code)
217
+ try:
218
+ return parser.parse(wrapped), -added
219
+ except draconic.DraconicSyntaxError:
220
+ return [], 0
221
+ except Exception as exc: # pragma: no cover - defensive
222
+ log.debug("Symbol extraction failed: %s", exc)
223
+ return [], 0
224
+
225
+
226
+ def _range_from_positions(
227
+ lineno: int | None,
228
+ col_offset: int | None,
229
+ end_lineno: int | None,
230
+ end_col_offset: int | None,
231
+ ) -> types.Range:
232
+ start_line = max((lineno or 1) - 1, 0)
233
+ start_char = max(col_offset or 0, 0)
234
+ end_line = max(((end_lineno or lineno or 1) - 1), 0)
235
+ raw_end_char = end_col_offset if end_col_offset is not None else col_offset
236
+ end_char = max(raw_end_char or start_char, start_char)
237
+ if end_char <= start_char:
238
+ end_char = start_char + 1
239
+ return types.Range(
240
+ start=types.Position(line=start_line, character=start_char),
241
+ end=types.Position(line=end_line, character=end_char),
242
+ )
243
+
244
+
245
+ def _shift_range(rng: types.Range, line_offset: int, char_offset: int = 0) -> types.Range:
246
+ start_char = rng.start.character + (char_offset if rng.start.line == 0 else 0)
247
+ end_char = rng.end.character + (char_offset if rng.end.line == 0 else 0)
248
+ if line_offset == 0:
249
+ return types.Range(
250
+ start=types.Position(line=rng.start.line, character=start_char),
251
+ end=types.Position(line=rng.end.line, character=end_char),
252
+ )
253
+ return types.Range(
254
+ start=types.Position(line=max(rng.start.line + line_offset, 0), character=max(start_char, 0)),
255
+ end=types.Position(line=max(rng.end.line + line_offset, 0), character=max(end_char, 0)),
256
+ )
257
+
258
+
259
+ def _wrap_draconic(code: str) -> tuple[str, int]:
260
+ indented = "\n".join(f" {line}" for line in code.splitlines())
261
+ wrapped = f"def __alias_main__():\n{indented}\n__alias_main__()"
262
+ return wrapped, 1
263
+
264
+
265
+ def _dedupe_ranges(ranges: Iterable[types.Range]) -> List[types.Range]:
266
+ seen = set()
267
+ unique: list[types.Range] = []
268
+ for rng in ranges:
269
+ key = (rng.start.line, rng.start.character, rng.end.line, rng.end.character)
270
+ if key in seen:
271
+ continue
272
+ seen.add(key)
273
+ unique.append(rng)
274
+ return unique
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: avrae-ls
3
- Version: 0.6.0
3
+ Version: 0.6.2
4
4
  Summary: Language server for Avrae draconic aliases
5
5
  Author: 1drturtle
6
6
  License: MIT License
@@ -0,0 +1,34 @@
1
+ avrae_ls/__init__.py,sha256=BmjrnksGkbG7TPqwbyQvgYj9uei8pFSFpfkRpaGVdJU,63
2
+ avrae_ls/__main__.py,sha256=N0Q61dKDCPd8RK-DnviK-44b949GzNBYi3nDuS4xxLE,8668
3
+ avrae_ls/alias_preview.py,sha256=pSDTZVmcZZbUqCxyCryVWFSGdN8p0fLkR4IqbHxgNMs,12376
4
+ avrae_ls/alias_tests.py,sha256=JFaWApVqxsHoF4QDm98kbYZRPTy5_QhsmUJq2kWV_I4,11917
5
+ avrae_ls/api.py,sha256=7QVJAmqgKkiS22qj39_aSVsDuO_kUkyl2Ho1IsD58yE,65110
6
+ avrae_ls/argparser.py,sha256=DRptXGyK4f0r7NsuV1Sg4apG-qihIClOX408jgk4HH4,13762
7
+ avrae_ls/argument_parsing.py,sha256=ezKl65VwuNEDxt6KlYwVQcpy1110UDvf4BqZqgZTcqk,2122
8
+ avrae_ls/code_actions.py,sha256=CYl3nMzCaE9k35p5fWAmzsTOVfcv2RrkA3SbtyCdcsI,9423
9
+ avrae_ls/codes.py,sha256=iPRPQ6i9DZheae4_ra1y29vCw3Y4SEu6Udf5WiZj_RY,136
10
+ avrae_ls/completions.py,sha256=FO9LCT1E5upSr23j0lUa_h_J1QN7ET9Yl_jLcWE97m0,64023
11
+ avrae_ls/config.py,sha256=B_WqsF2lWKsalWoEhogL9sxSym9r-XcPuo_Uyi27cl0,16984
12
+ avrae_ls/context.py,sha256=c18vdkuxKlPjPIogtWhJjbuynKLmgybD-VUkG46ooKM,12205
13
+ avrae_ls/cvars.py,sha256=0tcVbUHx_CKJ6aou3kEsKX37LRWAjkUWlqqIuSRFlXk,3197
14
+ avrae_ls/diagnostics.py,sha256=x5sZmpgje3G6UfOub2nQFv10lfjTb_7Qn_2SjqiGfLY,28072
15
+ avrae_ls/dice.py,sha256=DY7V7L-EwAXaCgddgVe9xU1s9lVtiw5Zc2reipNgdTk,874
16
+ avrae_ls/parser.py,sha256=iwdITmiBqRTszxI5avjzD-P0nrRQb1Hv5AiWl603_DU,2201
17
+ avrae_ls/runtime.py,sha256=2IbdZNZ7VCpjuU1lbbp8jTtroE6w7ii-r_erebJUl-A,26339
18
+ avrae_ls/server.py,sha256=pY4kIYUWY1q-yks9GEaHvsZk8ObwZRFMLG9cHtiEP70,17025
19
+ avrae_ls/signature_help.py,sha256=VSuLDExEEeNDc7yQZ_R6LXUcc95ErNCzoekiuMYEF6o,8654
20
+ avrae_ls/symbols.py,sha256=9oKcAHeHq1_hx2waKsfT5EWrcngXJdjYXcImAE4ingg,8672
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.6.2.dist-info/METADATA,sha256=R98dZGMitru9ZaX4XauObRgPA3G7sdOnHtvjmGOVkrI,7163
31
+ avrae_ls-0.6.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
32
+ avrae_ls-0.6.2.dist-info/entry_points.txt,sha256=OtYXipMQzqmxpMoApgo0MeJYFmMbkbFN51Ibhpb8hF4,52
33
+ avrae_ls-0.6.2.dist-info/licenses/LICENSE,sha256=O-0zMbcEi6wXz1DiSdVgzMlQjJcNqNe5KDv08uYzqR0,1055
34
+ avrae_ls-0.6.2.dist-info/RECORD,,
draconic/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ from .exceptions import *
2
+ from .helpers import DraconicConfig
3
+ from .interpreter import *
4
+ from . import utils
draconic/exceptions.py ADDED
@@ -0,0 +1,157 @@
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)