python-delphi-lsp 3.6.0__cp310-abi3-win_amd64.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.
Files changed (56) hide show
  1. delphi_lsp/__init__.py +184 -0
  2. delphi_lsp/_version.py +1 -0
  3. delphi_lsp/agent_cache.py +1574 -0
  4. delphi_lsp/agent_cli.py +902 -0
  5. delphi_lsp/agent_context.py +3907 -0
  6. delphi_lsp/agent_cpg.py +349 -0
  7. delphi_lsp/agent_cpg_builder.py +666 -0
  8. delphi_lsp/agent_layers.py +1410 -0
  9. delphi_lsp/agent_metrics.py +526 -0
  10. delphi_lsp/agent_protocol.py +445 -0
  11. delphi_lsp/agent_relations.py +1881 -0
  12. delphi_lsp/agent_templates.py +614 -0
  13. delphi_lsp/agent_wiki.py +2853 -0
  14. delphi_lsp/agent_workspace.py +860 -0
  15. delphi_lsp/ast_serialize.py +126 -0
  16. delphi_lsp/binary.py +250 -0
  17. delphi_lsp/comment_builder.py +28 -0
  18. delphi_lsp/consts.py +345 -0
  19. delphi_lsp/delphi_lsp_native.pyd +0 -0
  20. delphi_lsp/delphiast_lexer.py +399 -0
  21. delphi_lsp/delphiast_parser.py +2189 -0
  22. delphi_lsp/delphiast_tokens.py +41 -0
  23. delphi_lsp/grammar.py +459 -0
  24. delphi_lsp/incremental.py +1139 -0
  25. delphi_lsp/lark_builder.py +2684 -0
  26. delphi_lsp/lark_tokens.py +236 -0
  27. delphi_lsp/lexical_scanner.py +30 -0
  28. delphi_lsp/lsp_server.py +6698 -0
  29. delphi_lsp/metrics.py +1452 -0
  30. delphi_lsp/native_bridge.py +300 -0
  31. delphi_lsp/navigation_cache.py +189 -0
  32. delphi_lsp/nodes.py +462 -0
  33. delphi_lsp/parallel_outline.py +662 -0
  34. delphi_lsp/parse_artifacts.py +969 -0
  35. delphi_lsp/parser.py +342 -0
  36. delphi_lsp/parser_backend.py +139 -0
  37. delphi_lsp/preprocessor.py +1122 -0
  38. delphi_lsp/progress.py +26 -0
  39. delphi_lsp/project_config.py +285 -0
  40. delphi_lsp/project_discovery.py +932 -0
  41. delphi_lsp/project_indexer.py +470 -0
  42. delphi_lsp/py.typed +1 -0
  43. delphi_lsp/response_cache.py +465 -0
  44. delphi_lsp/semantic.py +394 -0
  45. delphi_lsp/semantic_builder.py +2302 -0
  46. delphi_lsp/source_reader.py +17 -0
  47. delphi_lsp/wiki_external_sort.py +392 -0
  48. delphi_lsp/wiki_memory.py +548 -0
  49. delphi_lsp/workspace.py +83 -0
  50. delphi_lsp/writer.py +73 -0
  51. python_delphi_lsp-3.6.0.dist-info/METADATA +766 -0
  52. python_delphi_lsp-3.6.0.dist-info/RECORD +56 -0
  53. python_delphi_lsp-3.6.0.dist-info/WHEEL +4 -0
  54. python_delphi_lsp-3.6.0.dist-info/entry_points.txt +3 -0
  55. python_delphi_lsp-3.6.0.dist-info/licenses/LICENSE +373 -0
  56. python_delphi_lsp-3.6.0.dist-info/sboms/python-delphi-lsp.cyclonedx.json +770 -0
@@ -0,0 +1,349 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ import json
5
+ import posixpath
6
+ import sys
7
+ import unicodedata
8
+ from collections import deque
9
+ from collections.abc import Iterable, Mapping, Sequence
10
+ from dataclasses import dataclass
11
+
12
+ _GRAPH_EDGE_LABELS = {
13
+ "ast": frozenset({"AST", "CONTAINS"}),
14
+ "cfg": frozenset({"CFG"}),
15
+ "dfg": frozenset({"DEF", "USE", "REACHING_DEF"}),
16
+ "call": frozenset({"CALL"}),
17
+ "full": None,
18
+ }
19
+
20
+
21
+ @dataclass(frozen=True, slots=True)
22
+ class CpgTarget:
23
+ target_id: str
24
+ source_path: str
25
+ path: str
26
+ unit_id: str
27
+ name: str
28
+ qualified_name: str
29
+ kind: str
30
+ line: int
31
+ column: int
32
+ end_line: int = 0
33
+ end_column: int = 0
34
+ visibility: str = "unknown"
35
+ type_name: str = ""
36
+ owner: str = ""
37
+ parent_target_id: str = ""
38
+
39
+
40
+ def _normalized_identity(value: object) -> object:
41
+ if isinstance(value, str):
42
+ normalized = unicodedata.normalize("NFC", value).replace("\\", "/")
43
+ if "/" in normalized:
44
+ normalized = posixpath.normpath(normalized)
45
+ return normalized.casefold()
46
+ if isinstance(value, Sequence) and not isinstance(value, (bytes, bytearray)):
47
+ return [_normalized_identity(item) for item in value]
48
+ return value
49
+
50
+
51
+ def _stable_id(prefix: str, *identity: object) -> str:
52
+ payload = json.dumps(
53
+ [_normalized_identity(value) for value in identity],
54
+ ensure_ascii=False,
55
+ separators=(",", ":"),
56
+ ).encode("utf-8")
57
+ return f"{prefix}_{hashlib.sha256(payload).hexdigest()}"
58
+
59
+
60
+ def _frozen_properties(
61
+ properties: Mapping[str, object] | None,
62
+ ) -> tuple[tuple[str, object], ...]:
63
+ if properties is None:
64
+ return ()
65
+ return tuple(sorted(((str(key), value) for key, value in properties.items())))
66
+
67
+
68
+ @dataclass(frozen=True, slots=True)
69
+ class CpgNode:
70
+ node_id: str
71
+ label: str
72
+ properties: tuple[tuple[str, object], ...] = ()
73
+
74
+ @classmethod
75
+ def create(
76
+ cls,
77
+ *,
78
+ label: str,
79
+ identity: Sequence[object],
80
+ properties: Mapping[str, object] | None = None,
81
+ ) -> CpgNode:
82
+ normalized_label = label.upper()
83
+ return cls(
84
+ _stable_id("cpg_node", normalized_label, tuple(identity)),
85
+ normalized_label,
86
+ _frozen_properties(properties),
87
+ )
88
+
89
+ def to_mapping(self) -> dict[str, object]:
90
+ return {
91
+ "item_type": "cpg_node",
92
+ "node_id": self.node_id,
93
+ "label": self.label,
94
+ "properties": dict(self.properties),
95
+ }
96
+
97
+
98
+ @dataclass(frozen=True, slots=True)
99
+ class CpgEdge:
100
+ edge_id: str
101
+ label: str
102
+ source: str
103
+ target: str
104
+ properties: tuple[tuple[str, object], ...] = ()
105
+
106
+ @classmethod
107
+ def create(
108
+ cls,
109
+ *,
110
+ label: str,
111
+ source: str,
112
+ target: str,
113
+ identity: Sequence[object] = (),
114
+ properties: Mapping[str, object] | None = None,
115
+ ) -> CpgEdge:
116
+ normalized_label = label.upper()
117
+ frozen = _frozen_properties(properties)
118
+ return cls(
119
+ _stable_id(
120
+ "cpg_edge",
121
+ normalized_label,
122
+ source,
123
+ target,
124
+ tuple(identity),
125
+ frozen,
126
+ ),
127
+ normalized_label,
128
+ source,
129
+ target,
130
+ frozen,
131
+ )
132
+
133
+ def to_mapping(self) -> dict[str, object]:
134
+ result: dict[str, object] = {
135
+ "item_type": "cpg_edge",
136
+ "edge_id": self.edge_id,
137
+ "label": self.label,
138
+ "source": self.source,
139
+ "target": self.target,
140
+ }
141
+ if self.properties:
142
+ result["properties"] = dict(self.properties)
143
+ return result
144
+
145
+
146
+ @dataclass(frozen=True, slots=True)
147
+ class CpgProblem:
148
+ kind: str
149
+ message: str
150
+ path: str = ""
151
+ line: int = 0
152
+ column: int = 0
153
+
154
+ def to_mapping(self) -> dict[str, object]:
155
+ result: dict[str, object] = {
156
+ "item_type": "cpg_problem",
157
+ "kind": self.kind,
158
+ "message": self.message,
159
+ }
160
+ if self.path:
161
+ result["path"] = self.path
162
+ if self.line > 0:
163
+ result["line"] = self.line
164
+ if self.column > 0:
165
+ result["column"] = self.column
166
+ return result
167
+
168
+
169
+ @dataclass(frozen=True, slots=True)
170
+ class CpgSubgraph:
171
+ target_id: str
172
+ graph: str
173
+ root_node_id: str
174
+ nodes: tuple[CpgNode, ...]
175
+ edges: tuple[CpgEdge, ...]
176
+ problems: tuple[CpgProblem, ...] = ()
177
+ truncated: bool = False
178
+ unresolved: int = 0
179
+
180
+ @classmethod
181
+ def create(
182
+ cls,
183
+ *,
184
+ target_id: str,
185
+ graph: str,
186
+ root_node_id: str,
187
+ nodes: Iterable[CpgNode] = (),
188
+ edges: Iterable[CpgEdge] = (),
189
+ problems: Iterable[CpgProblem] = (),
190
+ truncated: bool = False,
191
+ unresolved: int = 0,
192
+ record_limit: int = 50_000,
193
+ ) -> CpgSubgraph:
194
+ unique_nodes = {node.node_id: node for node in nodes}
195
+ ordered_nodes = sorted(unique_nodes.values(), key=lambda item: item.node_id)
196
+ unique_edges = {edge.edge_id: edge for edge in edges}
197
+ ordered_edges = sorted(unique_edges.values(), key=lambda item: item.edge_id)
198
+ unique_problems = {
199
+ (problem.kind, problem.message, problem.path, problem.line, problem.column): problem
200
+ for problem in problems
201
+ }
202
+ ordered_problems = sorted(
203
+ unique_problems.values(),
204
+ key=lambda item: (
205
+ item.path.casefold(),
206
+ item.line,
207
+ item.column,
208
+ item.kind,
209
+ item.message,
210
+ ),
211
+ )
212
+
213
+ limit = max(1, record_limit)
214
+ overflow = len(ordered_nodes) + len(ordered_edges) > limit
215
+ if overflow:
216
+ if len(ordered_nodes) > limit:
217
+ selected_nodes = ordered_nodes[:limit]
218
+ if root_node_id in unique_nodes and all(
219
+ node.node_id != root_node_id for node in selected_nodes
220
+ ):
221
+ selected_nodes[-1] = unique_nodes[root_node_id]
222
+ selected_nodes.sort(key=lambda item: item.node_id)
223
+ ordered_nodes = selected_nodes
224
+ ordered_edges = []
225
+ else:
226
+ remaining = limit - len(ordered_nodes)
227
+ ordered_edges = ordered_edges[:remaining]
228
+ retained_ids = {node.node_id for node in ordered_nodes}
229
+ ordered_edges = [
230
+ edge
231
+ for edge in ordered_edges
232
+ if edge.source in retained_ids and edge.target in retained_ids
233
+ ]
234
+ return cls(
235
+ target_id=target_id,
236
+ graph=graph,
237
+ root_node_id=root_node_id,
238
+ nodes=tuple(ordered_nodes),
239
+ edges=tuple(ordered_edges),
240
+ problems=tuple(ordered_problems),
241
+ truncated=truncated or overflow,
242
+ unresolved=max(0, unresolved),
243
+ )
244
+
245
+ @property
246
+ def retained_bytes(self) -> int:
247
+ property_slots = sum(len(node.properties) for node in self.nodes) + sum(
248
+ len(edge.properties) for edge in self.edges
249
+ )
250
+ text_bytes = sum(
251
+ len(node.node_id) + len(node.label)
252
+ for node in self.nodes
253
+ ) + sum(
254
+ len(edge.edge_id)
255
+ + len(edge.label)
256
+ + len(edge.source)
257
+ + len(edge.target)
258
+ for edge in self.edges
259
+ )
260
+ problem_bytes = sum(
261
+ len(problem.kind) + len(problem.message) + len(problem.path)
262
+ for problem in self.problems
263
+ )
264
+ return (
265
+ sys.getsizeof(self)
266
+ + len(self.nodes) * 256
267
+ + len(self.edges) * 320
268
+ + len(self.problems) * 256
269
+ + property_slots * 96
270
+ + text_bytes
271
+ + problem_bytes
272
+ )
273
+
274
+ def to_items(self) -> list[dict[str, object]]:
275
+ metadata: dict[str, object] = {
276
+ "item_type": "cpg_metadata",
277
+ "target_id": self.target_id,
278
+ "graph": self.graph,
279
+ "root_node_id": self.root_node_id,
280
+ "completeness": "sound_partial",
281
+ "nodes": len(self.nodes),
282
+ "edges": len(self.edges),
283
+ "unresolved": self.unresolved,
284
+ "problem_count": len(self.problems),
285
+ "truncated": self.truncated,
286
+ "retained_bytes": self.retained_bytes,
287
+ }
288
+ return [
289
+ metadata,
290
+ *(node.to_mapping() for node in self.nodes),
291
+ *(edge.to_mapping() for edge in self.edges),
292
+ *(problem.to_mapping() for problem in self.problems),
293
+ ]
294
+
295
+ def select(
296
+ self,
297
+ *,
298
+ graph: str,
299
+ direction: str,
300
+ depth: int,
301
+ root_node_id: str | None = None,
302
+ ) -> CpgSubgraph:
303
+ allowed = _GRAPH_EDGE_LABELS[graph]
304
+ candidate_edges = tuple(
305
+ edge for edge in self.edges if allowed is None or edge.label in allowed
306
+ )
307
+ root = root_node_id or self.root_node_id
308
+ reached = {root}
309
+ frontier = deque([(root, 0)])
310
+ selected_edges: dict[str, CpgEdge] = {}
311
+ outgoing: dict[str, list[CpgEdge]] = {}
312
+ incoming: dict[str, list[CpgEdge]] = {}
313
+ for edge in candidate_edges:
314
+ outgoing.setdefault(edge.source, []).append(edge)
315
+ incoming.setdefault(edge.target, []).append(edge)
316
+ while frontier:
317
+ current, distance = frontier.popleft()
318
+ if distance >= depth:
319
+ continue
320
+ adjacent: list[tuple[CpgEdge, str]] = []
321
+ if direction in {"out", "both"}:
322
+ adjacent.extend((edge, edge.target) for edge in outgoing.get(current, ()))
323
+ if direction in {"in", "both"}:
324
+ adjacent.extend((edge, edge.source) for edge in incoming.get(current, ()))
325
+ for edge, neighbor in adjacent:
326
+ selected_edges[edge.edge_id] = edge
327
+ if neighbor not in reached:
328
+ reached.add(neighbor)
329
+ frontier.append((neighbor, distance + 1))
330
+ selected_nodes = tuple(node for node in self.nodes if node.node_id in reached)
331
+ return CpgSubgraph.create(
332
+ target_id=self.target_id,
333
+ graph=graph,
334
+ root_node_id=root,
335
+ nodes=selected_nodes,
336
+ edges=selected_edges.values(),
337
+ problems=self.problems,
338
+ truncated=self.truncated,
339
+ unresolved=self.unresolved,
340
+ )
341
+
342
+
343
+ __all__ = [
344
+ "CpgEdge",
345
+ "CpgNode",
346
+ "CpgProblem",
347
+ "CpgSubgraph",
348
+ "CpgTarget",
349
+ ]