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,445 @@
1
+ from __future__ import annotations
2
+
3
+ import base64
4
+ import binascii
5
+ import hashlib
6
+ import json
7
+ import posixpath
8
+ import unicodedata
9
+ from collections.abc import Iterable, Mapping, Sequence
10
+ from dataclasses import dataclass
11
+
12
+ SCHEMA_VERSION = 3
13
+
14
+ SUPPORTED_ACTIONS = (
15
+ 'open',
16
+ 'find',
17
+ 'inspect',
18
+ 'trace',
19
+ 'focus',
20
+ 'problems',
21
+ 'metrics',
22
+ 'cpg',
23
+ )
24
+
25
+ SUPPORTED_DETAILS = (
26
+ 'summary',
27
+ 'declaration',
28
+ 'members',
29
+ 'context',
30
+ 'body',
31
+ 'implementations',
32
+ )
33
+
34
+ SUPPORTED_RELATIONS = (
35
+ 'references',
36
+ 'callers',
37
+ 'callees',
38
+ 'uses',
39
+ 'used_by',
40
+ 'inherits',
41
+ 'implements',
42
+ )
43
+
44
+ SUPPORTED_GRAPHS = (
45
+ 'ast',
46
+ 'cfg',
47
+ 'dfg',
48
+ 'call',
49
+ 'full',
50
+ )
51
+
52
+ SUPPORTED_DIRECTIONS = (
53
+ 'out',
54
+ 'in',
55
+ 'both',
56
+ )
57
+
58
+ _REQUEST_FIELDS = frozenset(
59
+ {
60
+ 'action',
61
+ 'query',
62
+ 'target_id',
63
+ 'project_id',
64
+ 'detail',
65
+ 'relation',
66
+ 'cursor',
67
+ 'max_items',
68
+ 'max_chars',
69
+ 'graph',
70
+ 'direction',
71
+ 'depth',
72
+ }
73
+ )
74
+
75
+
76
+ class AgentProtocolError(ValueError):
77
+ def __init__(self, code: str, message: str) -> None:
78
+ self.code = code
79
+ self.message = message
80
+ super().__init__(message)
81
+
82
+
83
+ @dataclass(frozen=True)
84
+ class AgentRequest:
85
+ action: str
86
+ query: str = ''
87
+ target_id: str = ''
88
+ project_id: str = ''
89
+ detail: str = 'summary'
90
+ relation: str | None = None
91
+ cursor: str = ''
92
+ max_items: int = 12
93
+ max_chars: int = 12000
94
+ graph: str = 'full'
95
+ direction: str = 'out'
96
+ depth: int = 4
97
+
98
+ @classmethod
99
+ def from_mapping(cls, mapping: Mapping[str, object]) -> AgentRequest:
100
+ if not isinstance(mapping, Mapping):
101
+ raise AgentProtocolError('invalid_request', 'Request must be a mapping.')
102
+
103
+ unknown_fields = sorted(set(mapping) - _REQUEST_FIELDS, key=str)
104
+ if unknown_fields:
105
+ names = ', '.join(str(field) for field in unknown_fields)
106
+ noun = 'field' if len(unknown_fields) == 1 else 'fields'
107
+ raise AgentProtocolError('unknown_field', f'Unknown request {noun}: {names}.')
108
+ if 'action' not in mapping:
109
+ raise AgentProtocolError('missing_field', 'Missing required request field: action.')
110
+
111
+ values = {
112
+ 'action': mapping['action'],
113
+ 'query': mapping.get('query', ''),
114
+ 'target_id': mapping.get('target_id', ''),
115
+ 'project_id': mapping.get('project_id', ''),
116
+ 'detail': mapping.get('detail', 'summary'),
117
+ 'relation': mapping.get('relation'),
118
+ 'cursor': mapping.get('cursor', ''),
119
+ 'max_items': mapping.get('max_items', 12),
120
+ 'max_chars': mapping.get('max_chars', 12000),
121
+ 'graph': mapping.get('graph', 'full'),
122
+ 'direction': mapping.get('direction', 'out'),
123
+ 'depth': mapping.get('depth', 4),
124
+ }
125
+
126
+ for field_name in (
127
+ 'action',
128
+ 'query',
129
+ 'target_id',
130
+ 'project_id',
131
+ 'detail',
132
+ 'cursor',
133
+ 'graph',
134
+ 'direction',
135
+ ):
136
+ if not isinstance(values[field_name], str):
137
+ raise AgentProtocolError('invalid_type', f"Field '{field_name}' must be a string.")
138
+
139
+ relation = values['relation']
140
+ if relation is not None and not isinstance(relation, str):
141
+ raise AgentProtocolError('invalid_type', "Field 'relation' must be a string or null.")
142
+
143
+ for field_name in ('max_items', 'max_chars', 'depth'):
144
+ value = values[field_name]
145
+ if isinstance(value, bool) or not isinstance(value, int):
146
+ raise AgentProtocolError('invalid_type', f"Field '{field_name}' must be an integer.")
147
+
148
+ action = values['action']
149
+ detail = values['detail']
150
+ graph = values['graph']
151
+ direction = values['direction']
152
+ if action not in SUPPORTED_ACTIONS:
153
+ raise AgentProtocolError('invalid_action', f'Unsupported action value: {action!r}.')
154
+ if detail not in SUPPORTED_DETAILS:
155
+ raise AgentProtocolError('invalid_detail', f'Unsupported detail value: {detail!r}.')
156
+ if relation is not None and relation not in SUPPORTED_RELATIONS:
157
+ raise AgentProtocolError('invalid_relation', f'Unsupported relation value: {relation!r}.')
158
+ if graph not in SUPPORTED_GRAPHS:
159
+ raise AgentProtocolError('invalid_graph', f'Unsupported graph value: {graph!r}.')
160
+ if direction not in SUPPORTED_DIRECTIONS:
161
+ raise AgentProtocolError(
162
+ 'invalid_direction',
163
+ f'Unsupported direction value: {direction!r}.',
164
+ )
165
+
166
+ max_items = values['max_items']
167
+ max_chars = values['max_chars']
168
+ depth = values['depth']
169
+ if not 1 <= max_items <= 50:
170
+ raise AgentProtocolError(
171
+ 'max_items_out_of_range',
172
+ "Field 'max_items' must be between 1 and 50.",
173
+ )
174
+ if not 256 <= max_chars <= 40000:
175
+ raise AgentProtocolError(
176
+ 'max_chars_out_of_range',
177
+ "Field 'max_chars' must be between 256 and 40000.",
178
+ )
179
+ if not 1 <= depth <= 16:
180
+ raise AgentProtocolError(
181
+ 'depth_out_of_range',
182
+ "Field 'depth' must be between 1 and 16.",
183
+ )
184
+
185
+ return cls(
186
+ action=action,
187
+ query=values['query'],
188
+ target_id=values['target_id'],
189
+ project_id=values['project_id'],
190
+ detail=detail,
191
+ relation=relation,
192
+ cursor=values['cursor'],
193
+ max_items=max_items,
194
+ max_chars=max_chars,
195
+ graph=graph,
196
+ direction=direction,
197
+ depth=depth,
198
+ )
199
+
200
+ def to_mapping(self) -> dict[str, object]:
201
+ return {
202
+ 'action': self.action,
203
+ 'query': self.query,
204
+ 'target_id': self.target_id,
205
+ 'project_id': self.project_id,
206
+ 'detail': self.detail,
207
+ 'relation': self.relation,
208
+ 'cursor': self.cursor,
209
+ 'max_items': self.max_items,
210
+ 'max_chars': self.max_chars,
211
+ 'graph': self.graph,
212
+ 'direction': self.direction,
213
+ 'depth': self.depth,
214
+ }
215
+
216
+
217
+ @dataclass(frozen=True)
218
+ class Focus:
219
+ project_id: str = ''
220
+ unit_id: str = ''
221
+ target_id: str = ''
222
+
223
+ def to_mapping(self) -> dict[str, str]:
224
+ return {
225
+ 'project_id': self.project_id,
226
+ 'unit_id': self.unit_id,
227
+ 'target_id': self.target_id,
228
+ }
229
+
230
+
231
+ @dataclass(frozen=True)
232
+ class Page:
233
+ returned: int = 0
234
+ total: int = 0
235
+ truncated: bool = False
236
+ next_cursor: str = ''
237
+
238
+ def to_mapping(self) -> dict[str, object]:
239
+ return {
240
+ 'returned': self.returned,
241
+ 'total': self.total,
242
+ 'truncated': self.truncated,
243
+ 'next_cursor': self.next_cursor,
244
+ }
245
+
246
+
247
+ @dataclass(frozen=True)
248
+ class ContextBudget:
249
+ chars: int
250
+
251
+ @property
252
+ def approx_tokens(self) -> int:
253
+ return (self.chars + 3) // 4
254
+
255
+ def to_mapping(self) -> dict[str, int]:
256
+ return {
257
+ 'chars': self.chars,
258
+ 'approx_tokens': self.approx_tokens,
259
+ }
260
+
261
+
262
+ @dataclass(frozen=True)
263
+ class AgentResponse:
264
+ workspace_revision: str
265
+ focus: Focus
266
+ result: object
267
+ page: Page
268
+ context: ContextBudget
269
+
270
+ @property
271
+ def schema(self) -> int:
272
+ return SCHEMA_VERSION
273
+
274
+ def to_mapping(self) -> dict[str, object]:
275
+ return {
276
+ 'schema': self.schema,
277
+ 'workspace_revision': self.workspace_revision,
278
+ 'focus': self.focus.to_mapping(),
279
+ 'result': self.result,
280
+ 'page': self.page.to_mapping(),
281
+ 'context': self.context.to_mapping(),
282
+ }
283
+
284
+
285
+ def make_target_id(
286
+ kind: str,
287
+ relative_path: str,
288
+ qualified_name: str,
289
+ ordinal: int = 0,
290
+ ) -> str:
291
+ normalized_path = posixpath.normpath(relative_path.replace('\\', '/'))
292
+ if normalized_path == '.':
293
+ normalized_path = ''
294
+ identity = [
295
+ unicodedata.normalize('NFC', kind).casefold(),
296
+ unicodedata.normalize('NFC', normalized_path).casefold(),
297
+ unicodedata.normalize('NFC', qualified_name).casefold(),
298
+ ordinal,
299
+ ]
300
+ encoded = json.dumps(identity, ensure_ascii=False, separators=(',', ':')).encode('utf-8')
301
+ return f'target_v2_{hashlib.sha256(encoded).hexdigest()}'
302
+
303
+
304
+ def encode_cursor(revision: str, offset: int, fingerprint: str) -> str:
305
+ if not isinstance(revision, str) or not isinstance(fingerprint, str):
306
+ raise AgentProtocolError('malformed_cursor', 'Cursor is malformed.')
307
+ if isinstance(offset, bool) or not isinstance(offset, int):
308
+ raise AgentProtocolError('malformed_cursor', 'Cursor is malformed.')
309
+ if offset < 0:
310
+ raise AgentProtocolError('negative_cursor', 'Cursor offset cannot be negative.')
311
+
312
+ payload = {
313
+ 'fingerprint': fingerprint,
314
+ 'offset': offset,
315
+ 'revision': revision,
316
+ 'schema': SCHEMA_VERSION,
317
+ }
318
+ encoded = json.dumps(
319
+ payload,
320
+ ensure_ascii=False,
321
+ sort_keys=True,
322
+ separators=(',', ':'),
323
+ ).encode('utf-8')
324
+ return base64.urlsafe_b64encode(encoded).decode('ascii').rstrip('=')
325
+
326
+
327
+ def decode_cursor(cursor: str, expected_revision: str, expected_fingerprint: str) -> int:
328
+ if (
329
+ not isinstance(cursor, str)
330
+ or not cursor
331
+ or not cursor.isascii()
332
+ or any(not (character.isalnum() or character in '-_') for character in cursor)
333
+ ):
334
+ raise AgentProtocolError('malformed_cursor', 'Cursor is malformed.')
335
+
336
+ try:
337
+ padding = '=' * (-len(cursor) % 4)
338
+ decoded = base64.b64decode(cursor + padding, altchars=b'-_', validate=True)
339
+ payload = json.loads(decoded.decode('utf-8'))
340
+ except (binascii.Error, UnicodeDecodeError, ValueError):
341
+ raise AgentProtocolError('malformed_cursor', 'Cursor is malformed.') from None
342
+
343
+ expected_fields = {'fingerprint', 'offset', 'revision', 'schema'}
344
+ if not isinstance(payload, dict) or set(payload) != expected_fields:
345
+ raise AgentProtocolError('malformed_cursor', 'Cursor is malformed.')
346
+ if type(payload['schema']) is not int or payload['schema'] != SCHEMA_VERSION:
347
+ raise AgentProtocolError('malformed_cursor', 'Cursor is malformed.')
348
+ if not isinstance(payload['revision'], str) or not isinstance(payload['fingerprint'], str):
349
+ raise AgentProtocolError('malformed_cursor', 'Cursor is malformed.')
350
+
351
+ offset = payload['offset']
352
+ if isinstance(offset, bool) or not isinstance(offset, int):
353
+ raise AgentProtocolError('malformed_cursor', 'Cursor is malformed.')
354
+ if offset < 0:
355
+ raise AgentProtocolError('negative_cursor', 'Cursor offset cannot be negative.')
356
+ if payload['revision'] != expected_revision:
357
+ raise AgentProtocolError('stale_cursor', 'Cursor workspace revision is stale.')
358
+ if payload['fingerprint'] != expected_fingerprint:
359
+ raise AgentProtocolError('cursor_mismatch', 'Cursor fingerprint does not match.')
360
+ return offset
361
+
362
+
363
+ def paginate_items(
364
+ items: Iterable[object],
365
+ revision: str,
366
+ fingerprint: str,
367
+ max_items: int,
368
+ max_chars: int,
369
+ cursor: str = '',
370
+ ) -> tuple[Page, list[object]]:
371
+ for field_name, value in (('max_items', max_items), ('max_chars', max_chars)):
372
+ if isinstance(value, bool) or not isinstance(value, int):
373
+ raise AgentProtocolError('invalid_type', f"Field '{field_name}' must be an integer.")
374
+ if not 1 <= max_items <= 50:
375
+ raise AgentProtocolError(
376
+ 'max_items_out_of_range',
377
+ "Field 'max_items' must be between 1 and 50.",
378
+ )
379
+ if not 256 <= max_chars <= 40000:
380
+ raise AgentProtocolError(
381
+ 'max_chars_out_of_range',
382
+ "Field 'max_chars' must be between 256 and 40000.",
383
+ )
384
+
385
+ offset = decode_cursor(cursor, revision, fingerprint) if cursor else 0
386
+ all_items = items if isinstance(items, Sequence) else tuple(items)
387
+ if offset > len(all_items):
388
+ raise AgentProtocolError('malformed_cursor', 'Cursor is malformed.')
389
+
390
+ selected: list[object] = []
391
+ selected_chars = 2 # Compact JSON brackets around the selected list.
392
+ for index in range(offset, len(all_items)):
393
+ if len(selected) >= max_items:
394
+ break
395
+ item = all_items[index]
396
+ try:
397
+ serialized = json.dumps(
398
+ item,
399
+ ensure_ascii=False,
400
+ sort_keys=True,
401
+ separators=(',', ':'),
402
+ allow_nan=False,
403
+ )
404
+ except (TypeError, ValueError):
405
+ raise AgentProtocolError('invalid_item', 'Item is not JSON-compatible.') from None
406
+
407
+ item_chars = len(serialized)
408
+ separator_chars = 1 if selected else 0
409
+ if item_chars > max_chars:
410
+ raise AgentProtocolError('item_too_large', 'Serialized item exceeds max_chars.')
411
+ if selected_chars + separator_chars + item_chars > max_chars:
412
+ if not selected:
413
+ raise AgentProtocolError('item_too_large', 'Serialized item exceeds max_chars.')
414
+ break
415
+ selected.append(item)
416
+ selected_chars += separator_chars + item_chars
417
+
418
+ next_offset = offset + len(selected)
419
+ truncated = next_offset < len(all_items)
420
+ next_cursor = encode_cursor(revision, next_offset, fingerprint) if truncated else ''
421
+ page = Page(
422
+ returned=len(selected),
423
+ total=len(all_items),
424
+ truncated=truncated,
425
+ next_cursor=next_cursor,
426
+ )
427
+ return page, selected
428
+
429
+
430
+ __all__ = [
431
+ 'SCHEMA_VERSION',
432
+ 'SUPPORTED_ACTIONS',
433
+ 'SUPPORTED_DETAILS',
434
+ 'SUPPORTED_RELATIONS',
435
+ 'AgentProtocolError',
436
+ 'AgentRequest',
437
+ 'Focus',
438
+ 'Page',
439
+ 'ContextBudget',
440
+ 'AgentResponse',
441
+ 'make_target_id',
442
+ 'encode_cursor',
443
+ 'decode_cursor',
444
+ 'paginate_items',
445
+ ]