agent-codinglanguage-mapper 1.0.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.
- agent_codinglanguage_mapper/__init__.py +20 -0
- agent_codinglanguage_mapper/__main__.py +4 -0
- agent_codinglanguage_mapper/_version.py +2 -0
- agent_codinglanguage_mapper/adapters/__init__.py +4 -0
- agent_codinglanguage_mapper/adapters/delphi.py +822 -0
- agent_codinglanguage_mapper/adapters/delphi_frontend/__init__.py +6 -0
- agent_codinglanguage_mapper/adapters/delphi_frontend/comment_builder.py +29 -0
- agent_codinglanguage_mapper/adapters/delphi_frontend/consts.py +345 -0
- agent_codinglanguage_mapper/adapters/delphi_frontend/grammar.py +460 -0
- agent_codinglanguage_mapper/adapters/delphi_frontend/lark_builder.py +2674 -0
- agent_codinglanguage_mapper/adapters/delphi_frontend/lark_tokens.py +237 -0
- agent_codinglanguage_mapper/adapters/delphi_frontend/lsp_server.py +1832 -0
- agent_codinglanguage_mapper/adapters/delphi_frontend/nodes.py +371 -0
- agent_codinglanguage_mapper/adapters/delphi_frontend/parser.py +193 -0
- agent_codinglanguage_mapper/adapters/delphi_frontend/preprocessor.py +997 -0
- agent_codinglanguage_mapper/adapters/delphi_frontend/project_discovery.py +518 -0
- agent_codinglanguage_mapper/adapters/delphi_frontend/project_indexer.py +319 -0
- agent_codinglanguage_mapper/adapters/delphi_frontend/semantic.py +375 -0
- agent_codinglanguage_mapper/adapters/delphi_frontend/semantic_builder.py +1384 -0
- agent_codinglanguage_mapper/adapters/delphi_frontend/source_reader.py +17 -0
- agent_codinglanguage_mapper/adapters/delphi_frontend/workspace.py +67 -0
- agent_codinglanguage_mapper/adapters/tree_sitter.py +1722 -0
- agent_codinglanguage_mapper/cli.py +138 -0
- agent_codinglanguage_mapper/discovery.py +1328 -0
- agent_codinglanguage_mapper/indexer.py +1102 -0
- agent_codinglanguage_mapper/integration.py +186 -0
- agent_codinglanguage_mapper/lsp_server.py +413 -0
- agent_codinglanguage_mapper/lsp_service.py +447 -0
- agent_codinglanguage_mapper/mapper.py +596 -0
- agent_codinglanguage_mapper/models.py +101 -0
- agent_codinglanguage_mapper/protocol.py +152 -0
- agent_codinglanguage_mapper/rendering.py +153 -0
- agent_codinglanguage_mapper/templates/opencode/plugins/codebase_map.ts +191 -0
- agent_codinglanguage_mapper/templates/skill/SKILL.md +52 -0
- agent_codinglanguage_mapper/worker.py +29 -0
- agent_codinglanguage_mapper/workspace.py +114 -0
- agent_codinglanguage_mapper-1.0.0.dist-info/METADATA +383 -0
- agent_codinglanguage_mapper-1.0.0.dist-info/RECORD +42 -0
- agent_codinglanguage_mapper-1.0.0.dist-info/WHEEL +5 -0
- agent_codinglanguage_mapper-1.0.0.dist-info/entry_points.txt +2 -0
- agent_codinglanguage_mapper-1.0.0.dist-info/licenses/LICENSE +373 -0
- agent_codinglanguage_mapper-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Callable, Iterable, Optional
|
|
7
|
+
|
|
8
|
+
from .consts import AttributeName, SyntaxNodeType
|
|
9
|
+
from .nodes import SyntaxNode
|
|
10
|
+
from .parser import DelphiParser
|
|
11
|
+
from .preprocessor import IncludeLoader
|
|
12
|
+
from .source_reader import read_source_text
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ProjectProblemType(str, Enum):
|
|
16
|
+
CANT_FIND_FILE = 'cant_find_file'
|
|
17
|
+
CANT_OPEN_FILE = 'cant_open_file'
|
|
18
|
+
CANT_PARSE_FILE = 'cant_parse_file'
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class ProjectProblem:
|
|
23
|
+
problem_type: ProjectProblemType
|
|
24
|
+
file_name: str
|
|
25
|
+
description: str
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class UnitErrorInfo:
|
|
30
|
+
line: int = 0
|
|
31
|
+
col: int = 0
|
|
32
|
+
error: str = ''
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class UnitInfo:
|
|
37
|
+
name: str
|
|
38
|
+
path: str
|
|
39
|
+
syntax_tree: Optional[SyntaxNode]
|
|
40
|
+
has_error: bool = False
|
|
41
|
+
error_info: UnitErrorInfo = field(default_factory=UnitErrorInfo)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class IncludeFileInfo:
|
|
46
|
+
name: str
|
|
47
|
+
path: str
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass
|
|
51
|
+
class ProjectIndexResult:
|
|
52
|
+
parsed_units: list[UnitInfo]
|
|
53
|
+
include_files: list[IncludeFileInfo]
|
|
54
|
+
problems: list[ProjectProblem]
|
|
55
|
+
not_found_units: list[str]
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
GetUnitSyntaxHook = Callable[[str], tuple[Optional[SyntaxNode], bool, bool]]
|
|
59
|
+
UnitParsedHook = Callable[[str, str, SyntaxNode, bool], bool]
|
|
60
|
+
SourceTransform = Callable[[str], str]
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class ProjectIndexer:
|
|
64
|
+
def __init__(
|
|
65
|
+
self,
|
|
66
|
+
*,
|
|
67
|
+
search_paths: Iterable[str] = (),
|
|
68
|
+
include_paths: Iterable[str] = (),
|
|
69
|
+
defines: Iterable[str] = (),
|
|
70
|
+
include_loader: IncludeLoader | None = None,
|
|
71
|
+
on_get_unit_syntax: GetUnitSyntaxHook | None = None,
|
|
72
|
+
on_unit_parsed: UnitParsedHook | None = None,
|
|
73
|
+
source_transform: SourceTransform | None = None,
|
|
74
|
+
) -> None:
|
|
75
|
+
self.search_paths = [Path(path) for path in search_paths]
|
|
76
|
+
self.include_paths = [Path(path) for path in include_paths]
|
|
77
|
+
self.defines = list(defines)
|
|
78
|
+
self.include_loader = include_loader
|
|
79
|
+
self.on_get_unit_syntax = on_get_unit_syntax
|
|
80
|
+
self.on_unit_parsed = on_unit_parsed
|
|
81
|
+
self.source_transform = source_transform
|
|
82
|
+
|
|
83
|
+
self._parsed_units: dict[str, UnitInfo] = {}
|
|
84
|
+
self._problems: list[ProjectProblem] = []
|
|
85
|
+
self._not_found_units: set[str] = set()
|
|
86
|
+
self._include_files: dict[str, IncludeFileInfo] = {}
|
|
87
|
+
self._project_folder: Path = Path('.')
|
|
88
|
+
self._aborting: bool = False
|
|
89
|
+
|
|
90
|
+
def index(self, file_name: str) -> ProjectIndexResult:
|
|
91
|
+
self._parsed_units = {}
|
|
92
|
+
self._problems = []
|
|
93
|
+
self._not_found_units = set()
|
|
94
|
+
self._include_files = {}
|
|
95
|
+
self._aborting = False
|
|
96
|
+
|
|
97
|
+
entry = Path(file_name).expanduser().resolve()
|
|
98
|
+
self._project_folder = entry.parent
|
|
99
|
+
|
|
100
|
+
is_project = entry.suffix.casefold() in {'.dpr', '.dpk'}
|
|
101
|
+
self._parse_unit(entry.stem, entry, is_project=is_project)
|
|
102
|
+
|
|
103
|
+
parsed_units = sorted(self._parsed_units.values(), key=lambda item: item.name.casefold())
|
|
104
|
+
include_files = sorted(self._include_files.values(), key=lambda item: (item.name.casefold(), item.path.casefold()))
|
|
105
|
+
problems = list(self._problems)
|
|
106
|
+
not_found = sorted(self._not_found_units, key=str.casefold)
|
|
107
|
+
|
|
108
|
+
return ProjectIndexResult(
|
|
109
|
+
parsed_units=parsed_units,
|
|
110
|
+
include_files=include_files,
|
|
111
|
+
problems=problems,
|
|
112
|
+
not_found_units=not_found,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
def _parse_unit(self, unit_name: str, file_path: Path, *, is_project: bool) -> None:
|
|
116
|
+
if self._aborting:
|
|
117
|
+
return
|
|
118
|
+
|
|
119
|
+
normalized_name = unit_name.casefold()
|
|
120
|
+
if normalized_name in self._parsed_units:
|
|
121
|
+
return
|
|
122
|
+
|
|
123
|
+
hook_tree: Optional[SyntaxNode] = None
|
|
124
|
+
do_parse_unit = True
|
|
125
|
+
if self.on_get_unit_syntax is not None:
|
|
126
|
+
hook_tree, do_parse_unit, do_abort = self.on_get_unit_syntax(str(file_path))
|
|
127
|
+
if do_abort:
|
|
128
|
+
self._aborting = True
|
|
129
|
+
return
|
|
130
|
+
|
|
131
|
+
syntax_tree: Optional[SyntaxNode] = hook_tree
|
|
132
|
+
from_parser = False
|
|
133
|
+
|
|
134
|
+
if syntax_tree is None and do_parse_unit:
|
|
135
|
+
source = self._read_file(file_path)
|
|
136
|
+
if source is None:
|
|
137
|
+
return
|
|
138
|
+
parser = DelphiParser(
|
|
139
|
+
include_paths=[str(path) for path in self.include_paths],
|
|
140
|
+
defines=self.defines,
|
|
141
|
+
include_loader=self._build_include_loader(),
|
|
142
|
+
)
|
|
143
|
+
try:
|
|
144
|
+
if self.source_transform is not None:
|
|
145
|
+
source = self.source_transform(source)
|
|
146
|
+
result = parser.parse(source, str(file_path), build_semantic=False)
|
|
147
|
+
syntax_tree = result.root
|
|
148
|
+
from_parser = True
|
|
149
|
+
except Exception as exc: # pragma: no cover - parser error branch
|
|
150
|
+
self._problems.append(
|
|
151
|
+
ProjectProblem(
|
|
152
|
+
problem_type=ProjectProblemType.CANT_PARSE_FILE,
|
|
153
|
+
file_name=str(file_path),
|
|
154
|
+
description=str(exc),
|
|
155
|
+
)
|
|
156
|
+
)
|
|
157
|
+
self._parsed_units[normalized_name] = UnitInfo(
|
|
158
|
+
name=unit_name,
|
|
159
|
+
path=str(file_path),
|
|
160
|
+
syntax_tree=None,
|
|
161
|
+
has_error=True,
|
|
162
|
+
error_info=UnitErrorInfo(error=str(exc)),
|
|
163
|
+
)
|
|
164
|
+
return
|
|
165
|
+
|
|
166
|
+
if syntax_tree is None:
|
|
167
|
+
return
|
|
168
|
+
|
|
169
|
+
actual_name = syntax_tree.get_attribute(AttributeName.anName) or unit_name
|
|
170
|
+
normalized_name = actual_name.casefold()
|
|
171
|
+
unit_info = UnitInfo(name=actual_name, path=str(file_path), syntax_tree=syntax_tree)
|
|
172
|
+
self._parsed_units[normalized_name] = unit_info
|
|
173
|
+
|
|
174
|
+
if self.on_unit_parsed is not None:
|
|
175
|
+
do_abort = self.on_unit_parsed(actual_name, str(file_path), syntax_tree, from_parser)
|
|
176
|
+
if do_abort:
|
|
177
|
+
self._aborting = True
|
|
178
|
+
return
|
|
179
|
+
|
|
180
|
+
for dep_name, dep_path in self._collect_dependencies(syntax_tree, file_path, is_project=is_project):
|
|
181
|
+
if self._aborting:
|
|
182
|
+
return
|
|
183
|
+
resolved = self._resolve_unit_path(dep_name, dep_path, relative_to=file_path.parent)
|
|
184
|
+
if resolved is None:
|
|
185
|
+
self._not_found_units.add(dep_name)
|
|
186
|
+
self._problems.append(
|
|
187
|
+
ProjectProblem(
|
|
188
|
+
problem_type=ProjectProblemType.CANT_FIND_FILE,
|
|
189
|
+
file_name=dep_name,
|
|
190
|
+
description=f'Unit not found: {dep_name}',
|
|
191
|
+
)
|
|
192
|
+
)
|
|
193
|
+
continue
|
|
194
|
+
self._parse_unit(dep_name, resolved, is_project=False)
|
|
195
|
+
|
|
196
|
+
def _read_file(self, file_path: Path) -> Optional[str]:
|
|
197
|
+
try:
|
|
198
|
+
return read_source_text(file_path)
|
|
199
|
+
except OSError as exc:
|
|
200
|
+
self._problems.append(
|
|
201
|
+
ProjectProblem(
|
|
202
|
+
problem_type=ProjectProblemType.CANT_OPEN_FILE,
|
|
203
|
+
file_name=str(file_path),
|
|
204
|
+
description=str(exc),
|
|
205
|
+
)
|
|
206
|
+
)
|
|
207
|
+
return None
|
|
208
|
+
|
|
209
|
+
def _collect_dependencies(
|
|
210
|
+
self,
|
|
211
|
+
syntax_tree: SyntaxNode,
|
|
212
|
+
file_path: Path,
|
|
213
|
+
*,
|
|
214
|
+
is_project: bool,
|
|
215
|
+
) -> list[tuple[str, Optional[str]]]:
|
|
216
|
+
deps: list[tuple[str, Optional[str]]] = []
|
|
217
|
+
if is_project:
|
|
218
|
+
self._append_units_from_node(syntax_tree, SyntaxNodeType.ntUses, deps)
|
|
219
|
+
self._append_units_from_node(syntax_tree, SyntaxNodeType.ntContains, deps)
|
|
220
|
+
return deps
|
|
221
|
+
|
|
222
|
+
intf = syntax_tree.find_node(SyntaxNodeType.ntInterface)
|
|
223
|
+
impl = syntax_tree.find_node(SyntaxNodeType.ntImplementation)
|
|
224
|
+
if intf is not None:
|
|
225
|
+
self._append_units_from_node(intf, SyntaxNodeType.ntUses, deps)
|
|
226
|
+
if impl is not None:
|
|
227
|
+
self._append_units_from_node(impl, SyntaxNodeType.ntUses, deps)
|
|
228
|
+
return deps
|
|
229
|
+
|
|
230
|
+
def _append_units_from_node(
|
|
231
|
+
self,
|
|
232
|
+
root: SyntaxNode,
|
|
233
|
+
node_type: SyntaxNodeType,
|
|
234
|
+
deps: list[tuple[str, Optional[str]]],
|
|
235
|
+
) -> None:
|
|
236
|
+
target = root.find_node(node_type)
|
|
237
|
+
if target is None:
|
|
238
|
+
return
|
|
239
|
+
for child in target.child_nodes:
|
|
240
|
+
if child.typ != SyntaxNodeType.ntUnit:
|
|
241
|
+
continue
|
|
242
|
+
name = child.get_attribute(AttributeName.anName)
|
|
243
|
+
if not name:
|
|
244
|
+
continue
|
|
245
|
+
dep_path = child.get_attribute(AttributeName.anPath) or None
|
|
246
|
+
deps.append((name, dep_path))
|
|
247
|
+
|
|
248
|
+
def _resolve_unit_path(self, unit_name: str, unit_path: Optional[str], *, relative_to: Path) -> Optional[Path]:
|
|
249
|
+
candidates: list[Path] = []
|
|
250
|
+
|
|
251
|
+
if unit_path:
|
|
252
|
+
hint = Path(unit_path)
|
|
253
|
+
if hint.is_absolute():
|
|
254
|
+
candidates.append(hint)
|
|
255
|
+
else:
|
|
256
|
+
candidates.append((relative_to / hint).resolve())
|
|
257
|
+
|
|
258
|
+
unit_file_names: list[str]
|
|
259
|
+
if Path(unit_name).suffix:
|
|
260
|
+
unit_file_names = [unit_name]
|
|
261
|
+
else:
|
|
262
|
+
unit_file_names = [f'{unit_name}.pas', f'{unit_name}.dpr', f'{unit_name}.dpk']
|
|
263
|
+
|
|
264
|
+
search_roots = [relative_to, self._project_folder, *self.search_paths, *self.include_paths]
|
|
265
|
+
for root in search_roots:
|
|
266
|
+
for file_name in unit_file_names:
|
|
267
|
+
candidates.append((root / file_name).resolve())
|
|
268
|
+
|
|
269
|
+
seen: set[str] = set()
|
|
270
|
+
for candidate in candidates:
|
|
271
|
+
key = str(candidate).casefold()
|
|
272
|
+
if key in seen:
|
|
273
|
+
continue
|
|
274
|
+
seen.add(key)
|
|
275
|
+
if candidate.exists() and candidate.is_file():
|
|
276
|
+
return candidate
|
|
277
|
+
return None
|
|
278
|
+
|
|
279
|
+
def _build_include_loader(self) -> IncludeLoader:
|
|
280
|
+
base_loader = self.include_loader or self._default_include_loader
|
|
281
|
+
|
|
282
|
+
def wrapped(parent_file: str, include_name: str) -> Optional[tuple[str, str]]:
|
|
283
|
+
resolved = base_loader(parent_file, include_name)
|
|
284
|
+
if resolved is None:
|
|
285
|
+
return None
|
|
286
|
+
content, resolved_path = resolved
|
|
287
|
+
cache_key = f'{include_name.casefold()}::{resolved_path.casefold()}'
|
|
288
|
+
self._include_files[cache_key] = IncludeFileInfo(name=include_name, path=resolved_path)
|
|
289
|
+
return content, resolved_path
|
|
290
|
+
|
|
291
|
+
return wrapped
|
|
292
|
+
|
|
293
|
+
def _default_include_loader(self, parent_file: str, include_name: str) -> Optional[tuple[str, str]]:
|
|
294
|
+
parent = Path(parent_file).resolve().parent
|
|
295
|
+
include_path = Path(include_name.replace('\\', '/'))
|
|
296
|
+
search_roots = [parent, self._project_folder, *self.include_paths, *self.search_paths]
|
|
297
|
+
for root in search_roots:
|
|
298
|
+
candidate = (root / include_path).resolve()
|
|
299
|
+
if candidate.exists() and candidate.is_file():
|
|
300
|
+
try:
|
|
301
|
+
content = read_source_text(candidate)
|
|
302
|
+
except OSError:
|
|
303
|
+
continue
|
|
304
|
+
return content, str(candidate)
|
|
305
|
+
return None
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
__all__ = [
|
|
309
|
+
'ProjectIndexer',
|
|
310
|
+
'ProjectIndexResult',
|
|
311
|
+
'ProjectProblemType',
|
|
312
|
+
'ProjectProblem',
|
|
313
|
+
'UnitInfo',
|
|
314
|
+
'UnitErrorInfo',
|
|
315
|
+
'IncludeFileInfo',
|
|
316
|
+
'GetUnitSyntaxHook',
|
|
317
|
+
'UnitParsedHook',
|
|
318
|
+
'SourceTransform',
|
|
319
|
+
]
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def normalize_name(name: str) -> str:
|
|
9
|
+
return name.casefold()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True)
|
|
13
|
+
class SourceRange:
|
|
14
|
+
file_name: str
|
|
15
|
+
start_line: int
|
|
16
|
+
start_col: int
|
|
17
|
+
end_line: int
|
|
18
|
+
end_col: int
|
|
19
|
+
|
|
20
|
+
def contains(self, line: int, col: int) -> bool:
|
|
21
|
+
if line < self.start_line or line > self.end_line:
|
|
22
|
+
return False
|
|
23
|
+
if line == self.start_line and col < self.start_col:
|
|
24
|
+
return False
|
|
25
|
+
if line == self.end_line and col > self.end_col:
|
|
26
|
+
return False
|
|
27
|
+
return True
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Visibility(Enum):
|
|
31
|
+
UNKNOWN = 'unknown'
|
|
32
|
+
PRIVATE = 'private'
|
|
33
|
+
PROTECTED = 'protected'
|
|
34
|
+
PUBLIC = 'public'
|
|
35
|
+
PUBLISHED = 'published'
|
|
36
|
+
STRICT_PRIVATE = 'strict_private'
|
|
37
|
+
STRICT_PROTECTED = 'strict_protected'
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class SymbolKind(Enum):
|
|
41
|
+
UNIT = 'unit'
|
|
42
|
+
MODULE = 'module'
|
|
43
|
+
NAMESPACE = 'namespace'
|
|
44
|
+
PACKAGE = 'package'
|
|
45
|
+
TYPE = 'type'
|
|
46
|
+
TYPE_PARAMETER = 'type_parameter'
|
|
47
|
+
CLASS = 'class'
|
|
48
|
+
RECORD = 'record'
|
|
49
|
+
INTERFACE = 'interface'
|
|
50
|
+
ENUM = 'enum'
|
|
51
|
+
ENUM_VALUE = 'enum_value'
|
|
52
|
+
FIELD = 'field'
|
|
53
|
+
PROPERTY = 'property'
|
|
54
|
+
METHOD = 'method'
|
|
55
|
+
FUNCTION = 'function'
|
|
56
|
+
PROCEDURE = 'procedure'
|
|
57
|
+
CONSTRUCTOR = 'constructor'
|
|
58
|
+
DESTRUCTOR = 'destructor'
|
|
59
|
+
VARIABLE = 'variable'
|
|
60
|
+
PARAMETER = 'parameter'
|
|
61
|
+
CONSTANT = 'constant'
|
|
62
|
+
LABEL = 'label'
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class ScopeKind(Enum):
|
|
66
|
+
UNIT = 'unit'
|
|
67
|
+
TYPE = 'type'
|
|
68
|
+
ROUTINE = 'routine'
|
|
69
|
+
BLOCK = 'block'
|
|
70
|
+
WITH = 'with'
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class Modifier(Enum):
|
|
74
|
+
ABSTRACT = 'abstract'
|
|
75
|
+
SEALED = 'sealed'
|
|
76
|
+
FINAL = 'final'
|
|
77
|
+
VIRTUAL = 'virtual'
|
|
78
|
+
OVERRIDE = 'override'
|
|
79
|
+
OVERLOAD = 'overload'
|
|
80
|
+
REINTRODUCE = 'reintroduce'
|
|
81
|
+
INLINE = 'inline'
|
|
82
|
+
STATIC = 'static'
|
|
83
|
+
CLASS = 'class'
|
|
84
|
+
EXTERNAL = 'external'
|
|
85
|
+
FORWARD = 'forward'
|
|
86
|
+
DEPRECATED = 'deprecated'
|
|
87
|
+
NORETURN = 'noreturn'
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class ReferenceKind(Enum):
|
|
91
|
+
UNKNOWN = 'unknown'
|
|
92
|
+
VALUE = 'value'
|
|
93
|
+
TYPE = 'type'
|
|
94
|
+
CALL = 'call'
|
|
95
|
+
PROPERTY = 'property'
|
|
96
|
+
UNIT = 'unit'
|
|
97
|
+
LABEL = 'label'
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@dataclass(frozen=True)
|
|
101
|
+
class TypeRef:
|
|
102
|
+
def display_name(self) -> str:
|
|
103
|
+
return '<unknown>'
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@dataclass(frozen=True)
|
|
107
|
+
class UnknownTypeRef(TypeRef):
|
|
108
|
+
reason: str = ''
|
|
109
|
+
|
|
110
|
+
def display_name(self) -> str:
|
|
111
|
+
return '<unknown>' if not self.reason else f'<unknown:{self.reason}>'
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@dataclass(frozen=True)
|
|
115
|
+
class NamedTypeRef(TypeRef):
|
|
116
|
+
name: str
|
|
117
|
+
unit_name: str | None = None
|
|
118
|
+
|
|
119
|
+
def display_name(self) -> str:
|
|
120
|
+
if self.unit_name:
|
|
121
|
+
return f'{self.unit_name}.{self.name}'
|
|
122
|
+
return self.name
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@dataclass(frozen=True)
|
|
126
|
+
class PointerTypeRef(TypeRef):
|
|
127
|
+
target: TypeRef
|
|
128
|
+
|
|
129
|
+
def display_name(self) -> str:
|
|
130
|
+
return f'^{self.target.display_name()}'
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@dataclass(frozen=True)
|
|
134
|
+
class ClassOfTypeRef(TypeRef):
|
|
135
|
+
target: TypeRef
|
|
136
|
+
|
|
137
|
+
def display_name(self) -> str:
|
|
138
|
+
return f'class of {self.target.display_name()}'
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@dataclass(frozen=True)
|
|
142
|
+
class ReferenceTypeRef(TypeRef):
|
|
143
|
+
target: TypeRef
|
|
144
|
+
|
|
145
|
+
def display_name(self) -> str:
|
|
146
|
+
return f'reference to {self.target.display_name()}'
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
@dataclass(frozen=True)
|
|
150
|
+
class FileTypeRef(TypeRef):
|
|
151
|
+
element_type: TypeRef | None = None
|
|
152
|
+
|
|
153
|
+
def display_name(self) -> str:
|
|
154
|
+
if self.element_type is None:
|
|
155
|
+
return 'file'
|
|
156
|
+
return f'file of {self.element_type.display_name()}'
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@dataclass(frozen=True)
|
|
160
|
+
class GenericInstanceTypeRef(TypeRef):
|
|
161
|
+
base: NamedTypeRef
|
|
162
|
+
args: tuple[TypeRef, ...]
|
|
163
|
+
|
|
164
|
+
def display_name(self) -> str:
|
|
165
|
+
arg_list = ', '.join(arg.display_name() for arg in self.args)
|
|
166
|
+
return f'{self.base.display_name()}<{arg_list}>'
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
@dataclass(frozen=True)
|
|
170
|
+
class ArrayTypeRef(TypeRef):
|
|
171
|
+
element_type: TypeRef
|
|
172
|
+
dimensions: tuple[TypeRef, ...] = ()
|
|
173
|
+
is_dynamic: bool = False
|
|
174
|
+
|
|
175
|
+
def display_name(self) -> str:
|
|
176
|
+
if self.is_dynamic or not self.dimensions:
|
|
177
|
+
return f'array of {self.element_type.display_name()}'
|
|
178
|
+
dims = ', '.join(dim.display_name() for dim in self.dimensions)
|
|
179
|
+
return f'array[{dims}] of {self.element_type.display_name()}'
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
@dataclass(frozen=True)
|
|
183
|
+
class SetTypeRef(TypeRef):
|
|
184
|
+
element_type: TypeRef
|
|
185
|
+
|
|
186
|
+
def display_name(self) -> str:
|
|
187
|
+
return f'set of {self.element_type.display_name()}'
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
@dataclass(frozen=True)
|
|
191
|
+
class SubrangeTypeRef(TypeRef):
|
|
192
|
+
lower: str
|
|
193
|
+
upper: str
|
|
194
|
+
|
|
195
|
+
def display_name(self) -> str:
|
|
196
|
+
return f'{self.lower}..{self.upper}'
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
@dataclass(frozen=True)
|
|
200
|
+
class EnumTypeRef(TypeRef):
|
|
201
|
+
name: str
|
|
202
|
+
members: tuple[str, ...]
|
|
203
|
+
|
|
204
|
+
def display_name(self) -> str:
|
|
205
|
+
return self.name if self.name else 'enum'
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
@dataclass(frozen=True)
|
|
209
|
+
class RecordTypeRef(TypeRef):
|
|
210
|
+
name: str
|
|
211
|
+
scope: Optional['Scope'] = None
|
|
212
|
+
packed: bool = False
|
|
213
|
+
|
|
214
|
+
def display_name(self) -> str:
|
|
215
|
+
return self.name if self.name else 'record'
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
@dataclass(frozen=True)
|
|
219
|
+
class ClassTypeRef(TypeRef):
|
|
220
|
+
name: str
|
|
221
|
+
scope: Optional['Scope'] = None
|
|
222
|
+
bases: tuple[TypeRef, ...] = ()
|
|
223
|
+
is_abstract: bool = False
|
|
224
|
+
|
|
225
|
+
def display_name(self) -> str:
|
|
226
|
+
return self.name if self.name else 'class'
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
@dataclass(frozen=True)
|
|
230
|
+
class InterfaceTypeRef(TypeRef):
|
|
231
|
+
name: str
|
|
232
|
+
scope: Optional['Scope'] = None
|
|
233
|
+
bases: tuple[TypeRef, ...] = ()
|
|
234
|
+
|
|
235
|
+
def display_name(self) -> str:
|
|
236
|
+
return self.name if self.name else 'interface'
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
@dataclass(frozen=True)
|
|
240
|
+
class TypeParameterRef(TypeRef):
|
|
241
|
+
name: str
|
|
242
|
+
|
|
243
|
+
def display_name(self) -> str:
|
|
244
|
+
return self.name
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
@dataclass(frozen=True)
|
|
248
|
+
class ProcTypeRef(TypeRef):
|
|
249
|
+
name: str
|
|
250
|
+
params: tuple[TypeRef, ...] = ()
|
|
251
|
+
return_type: TypeRef | None = None
|
|
252
|
+
|
|
253
|
+
def display_name(self) -> str:
|
|
254
|
+
params = ', '.join(p.display_name() for p in self.params)
|
|
255
|
+
if self.return_type is None:
|
|
256
|
+
return f'procedure({params})'
|
|
257
|
+
return f'function({params}): {self.return_type.display_name()}'
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
@dataclass
|
|
261
|
+
class Symbol:
|
|
262
|
+
name: str
|
|
263
|
+
kind: SymbolKind
|
|
264
|
+
decl_range: SourceRange
|
|
265
|
+
name_range: SourceRange
|
|
266
|
+
scope: 'Scope'
|
|
267
|
+
visibility: Visibility = Visibility.UNKNOWN
|
|
268
|
+
type_ref: TypeRef = field(default_factory=UnknownTypeRef)
|
|
269
|
+
modifiers: set[Modifier] = field(default_factory=set)
|
|
270
|
+
attributes: dict[str, str] = field(default_factory=dict)
|
|
271
|
+
doc: str = ''
|
|
272
|
+
overloads: list['Symbol'] = field(default_factory=list)
|
|
273
|
+
member_scope: Optional['Scope'] = None
|
|
274
|
+
base_types: tuple[TypeRef, ...] = ()
|
|
275
|
+
|
|
276
|
+
def add_overload(self, symbol: 'Symbol') -> None:
|
|
277
|
+
self.overloads.append(symbol)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
@dataclass
|
|
281
|
+
class SymbolReference:
|
|
282
|
+
name: str
|
|
283
|
+
kind: ReferenceKind
|
|
284
|
+
ref_range: SourceRange
|
|
285
|
+
scope: 'Scope'
|
|
286
|
+
resolved: Optional[Symbol] = None
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
@dataclass
|
|
290
|
+
class Scope:
|
|
291
|
+
kind: ScopeKind
|
|
292
|
+
name: str
|
|
293
|
+
parent: Optional['Scope'] = None
|
|
294
|
+
owner: Optional[Symbol] = None
|
|
295
|
+
symbols: dict[str, list[Symbol]] = field(default_factory=dict)
|
|
296
|
+
imports: list['Scope'] = field(default_factory=list)
|
|
297
|
+
with_scopes: list['Scope'] = field(default_factory=list)
|
|
298
|
+
|
|
299
|
+
def define(self, symbol: Symbol) -> None:
|
|
300
|
+
key = normalize_name(symbol.name)
|
|
301
|
+
if key not in self.symbols:
|
|
302
|
+
self.symbols[key] = []
|
|
303
|
+
self.symbols[key].append(symbol)
|
|
304
|
+
|
|
305
|
+
def lookup_local(self, name: str) -> list[Symbol]:
|
|
306
|
+
return list(self.symbols.get(normalize_name(name), []))
|
|
307
|
+
|
|
308
|
+
def resolve(self, name: str) -> list[Symbol]:
|
|
309
|
+
results: list[Symbol] = []
|
|
310
|
+
results.extend(self.lookup_local(name))
|
|
311
|
+
if results:
|
|
312
|
+
return results
|
|
313
|
+
for scope in self.with_scopes:
|
|
314
|
+
scoped = scope.lookup_local(name)
|
|
315
|
+
if scoped:
|
|
316
|
+
return scoped
|
|
317
|
+
if self.parent is not None:
|
|
318
|
+
results.extend(self.parent.resolve(name))
|
|
319
|
+
if results:
|
|
320
|
+
return results
|
|
321
|
+
for scope in self.imports:
|
|
322
|
+
scoped = scope.lookup_local(name)
|
|
323
|
+
if scoped:
|
|
324
|
+
results.extend(scoped)
|
|
325
|
+
return results
|
|
326
|
+
|
|
327
|
+
def add_import(self, scope: 'Scope') -> None:
|
|
328
|
+
if scope not in self.imports:
|
|
329
|
+
self.imports.append(scope)
|
|
330
|
+
|
|
331
|
+
def add_with_scope(self, scope: 'Scope') -> None:
|
|
332
|
+
if scope not in self.with_scopes:
|
|
333
|
+
self.with_scopes.append(scope)
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
@dataclass
|
|
337
|
+
class SymbolIndex:
|
|
338
|
+
units: dict[str, Scope] = field(default_factory=dict)
|
|
339
|
+
name_index: dict[str, list[Symbol]] = field(default_factory=dict)
|
|
340
|
+
|
|
341
|
+
def register_unit(self, unit_name: str, unit_scope: Scope, *, index_symbols: bool = True) -> None:
|
|
342
|
+
self.units[normalize_name(unit_name)] = unit_scope
|
|
343
|
+
if index_symbols:
|
|
344
|
+
self._index_scope(unit_scope)
|
|
345
|
+
|
|
346
|
+
def lookup(self, name: str) -> list[Symbol]:
|
|
347
|
+
return list(self.name_index.get(normalize_name(name), []))
|
|
348
|
+
|
|
349
|
+
def lookup_unit(self, unit_name: str) -> Optional[Scope]:
|
|
350
|
+
return self.units.get(normalize_name(unit_name))
|
|
351
|
+
|
|
352
|
+
def resolve_qualified(self, qualified_name: str) -> list[Symbol]:
|
|
353
|
+
parts = qualified_name.split('.')
|
|
354
|
+
if len(parts) < 2:
|
|
355
|
+
return self.lookup(qualified_name)
|
|
356
|
+
unit_name = '.'.join(parts[:-1])
|
|
357
|
+
symbol_name = parts[-1]
|
|
358
|
+
unit_scope = self.lookup_unit(unit_name)
|
|
359
|
+
if unit_scope is None:
|
|
360
|
+
return []
|
|
361
|
+
return unit_scope.lookup_local(symbol_name)
|
|
362
|
+
|
|
363
|
+
def _index_scope(self, scope: Scope, seen: set[int] | None = None) -> None:
|
|
364
|
+
if seen is None:
|
|
365
|
+
seen = set()
|
|
366
|
+
scope_id = id(scope)
|
|
367
|
+
if scope_id in seen:
|
|
368
|
+
return
|
|
369
|
+
seen.add(scope_id)
|
|
370
|
+
for symbols in scope.symbols.values():
|
|
371
|
+
for symbol in symbols:
|
|
372
|
+
key = normalize_name(symbol.name)
|
|
373
|
+
self.name_index.setdefault(key, []).append(symbol)
|
|
374
|
+
for child_scope in scope.imports:
|
|
375
|
+
self._index_scope(child_scope, seen)
|