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,17 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+
6
+ def read_source_text(path: Path) -> str:
7
+ data = path.read_bytes()
8
+ if data.startswith(b'\xff\xfe') or data.startswith(b'\xfe\xff'):
9
+ text = data.decode('utf-16')
10
+ elif data.startswith(b'\xef\xbb\xbf'):
11
+ text = data.decode('utf-8-sig')
12
+ else:
13
+ try:
14
+ text = data.decode('utf-8')
15
+ except UnicodeDecodeError:
16
+ text = data.decode('latin-1')
17
+ return text.replace('\r\n', '\n').replace('\r', '\n')
@@ -0,0 +1,392 @@
1
+ """Streaming JSONL sorter for wiki export artifacts.
2
+
3
+ The implementation keeps memory usage bounded by writing bounded sorted runs to
4
+ temporary files and performing a streaming heap merge. When a system ``sort``
5
+ command is available, it is preferred to keep the Python heap cold.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import os
12
+ import shutil
13
+ import subprocess
14
+ import tempfile
15
+ from collections.abc import Callable, Iterator
16
+ from heapq import heappop, heappush
17
+ from itertools import count
18
+ from pathlib import Path
19
+ from subprocess import SubprocessError
20
+ from typing import Any, TextIO
21
+
22
+ SortKey = Callable[[dict[str, Any]], tuple[Any, ...] | list[Any] | Any]
23
+ _SYSTEM_SORT_MEMORY = "16M"
24
+
25
+
26
+ class WikiExternalSortError(RuntimeError):
27
+ """Raised when a JSONL record or key cannot be sorted deterministically."""
28
+
29
+
30
+ def sort_jsonl_records(
31
+ input_path: str | Path,
32
+ output_path: str | Path,
33
+ *,
34
+ key: SortKey | None = None,
35
+ fields: tuple[str, ...] | list[str] | None = None,
36
+ max_records_per_run: int = 50_000,
37
+ max_bytes_per_run: int = 8 * 1024 * 1024,
38
+ temp_dir: str | Path | None = None,
39
+ heartbeat: Callable[[], object] | None = None,
40
+ force_heartbeat: Callable[[], object] | None = None,
41
+ ) -> None:
42
+ """Sort JSONL ``input_path`` into ``output_path`` deterministically.
43
+
44
+ Either ``key`` or ``fields`` must be provided. ``fields`` must be an ordered
45
+ tuple/list of object keys used as a fixed sort tuple.
46
+ """
47
+
48
+ if not isinstance(max_records_per_run, int) or max_records_per_run <= 0:
49
+ raise ValueError("max_records_per_run must be a positive integer")
50
+ if not isinstance(max_bytes_per_run, int) or max_bytes_per_run <= 0:
51
+ raise ValueError("max_bytes_per_run must be a positive integer")
52
+
53
+ input_path = Path(input_path)
54
+ output_path = Path(output_path)
55
+ if not input_path.is_file():
56
+ raise FileNotFoundError(f"input JSONL path does not exist: {input_path}")
57
+ if output_path.exists() and output_path.is_dir():
58
+ raise ValueError("output_path must be a file path")
59
+
60
+ if key is None:
61
+ if not fields:
62
+ raise ValueError("key or fields must be provided")
63
+ field_names = tuple(fields)
64
+ if not all(isinstance(field, str) for field in field_names):
65
+ raise TypeError("fields must contain only strings")
66
+
67
+ def key(record: dict[str, Any]) -> tuple[Any, ...]:
68
+ return tuple(record.get(field) for field in field_names)
69
+
70
+ try:
71
+ temp_root = Path(tempfile.mkdtemp(dir=temp_dir))
72
+ try:
73
+ if _system_sort_available():
74
+ # Prefer external sort. The caller receives deterministic output
75
+ # either from this path or fallback merge below.
76
+ _system_sort_records(
77
+ input_path,
78
+ output_path,
79
+ key=key,
80
+ temp_root=temp_root,
81
+ heartbeat=heartbeat,
82
+ force_heartbeat=force_heartbeat,
83
+ )
84
+ return
85
+ except (OSError, SubprocessError, TypeError, UnicodeError, ValueError, WikiExternalSortError):
86
+ # A missing or incompatible platform sorter is exactly what the
87
+ # bounded Python merge path is for.
88
+ pass
89
+
90
+ runs = _build_sorted_runs(
91
+ input_path,
92
+ key=key,
93
+ max_records_per_run=max_records_per_run,
94
+ max_bytes_per_run=max_bytes_per_run,
95
+ temp_root=temp_root,
96
+ heartbeat=heartbeat,
97
+ )
98
+ _merge_sorted_runs(
99
+ runs,
100
+ output_path=output_path,
101
+ key=key,
102
+ heartbeat=heartbeat,
103
+ )
104
+ finally:
105
+ temp_root_path = locals().get("temp_root")
106
+ if temp_root_path is not None:
107
+ shutil.rmtree(temp_root_path, ignore_errors=True)
108
+
109
+
110
+ def _system_sort_available() -> bool:
111
+ return shutil.which("sort") is not None
112
+
113
+
114
+ def _system_sort_records(
115
+ input_path: Path,
116
+ output_path: Path,
117
+ *,
118
+ key: SortKey,
119
+ temp_root: Path,
120
+ heartbeat: Callable[[], object] | None,
121
+ force_heartbeat: Callable[[], object] | None,
122
+ ) -> None:
123
+ keyed_path = temp_root / "system-sort-keyed.jsonl"
124
+ sorted_path = temp_root / "system-sort-sorted.jsonl"
125
+
126
+ key_stream = output_with_keys(input_path, key)
127
+ with keyed_path.open("w", encoding="utf-8") as handle:
128
+ for key_text, index, raw in key_stream:
129
+ handle.write(f"{key_text}\x1f{index:020d}\x1f{raw}\n")
130
+ if heartbeat is not None and (index + 1) % 50_000 == 0:
131
+ heartbeat()
132
+
133
+ sort_binary = shutil.which("sort") or "sort"
134
+ command = [
135
+ sort_binary,
136
+ "-S",
137
+ _SYSTEM_SORT_MEMORY,
138
+ "--parallel",
139
+ "1",
140
+ "-T",
141
+ str(temp_root),
142
+ "-s",
143
+ "-t",
144
+ "\x1f",
145
+ "-k1,1",
146
+ "-k2,2",
147
+ str(keyed_path),
148
+ ]
149
+ env = os.environ.copy()
150
+ env["LC_ALL"] = "C"
151
+
152
+ stderr = ""
153
+ with sorted_path.open("w", encoding="utf-8") as sorted_handle:
154
+ if heartbeat is None:
155
+ result = subprocess.run(
156
+ command,
157
+ check=False,
158
+ stdout=sorted_handle,
159
+ stderr=subprocess.PIPE,
160
+ text=True,
161
+ env=env,
162
+ )
163
+ returncode = result.returncode
164
+ stderr = result.stderr
165
+ else:
166
+ with tempfile.TemporaryFile(mode="w+", encoding="utf-8") as stderr_handle:
167
+ process = subprocess.Popen(
168
+ command,
169
+ stdout=sorted_handle,
170
+ stderr=stderr_handle,
171
+ text=True,
172
+ env=env,
173
+ )
174
+ try:
175
+ (force_heartbeat or heartbeat)()
176
+ while True:
177
+ try:
178
+ returncode = process.wait(timeout=0.5)
179
+ break
180
+ except subprocess.TimeoutExpired:
181
+ heartbeat()
182
+ except BaseException:
183
+ process.terminate()
184
+ process.wait()
185
+ raise
186
+ stderr_handle.seek(0)
187
+ stderr = stderr_handle.read()
188
+ if returncode != 0:
189
+ raise WikiExternalSortError(
190
+ "system sort failed" + (f": {stderr.strip()}" if stderr else "")
191
+ )
192
+
193
+ temp_output_path = _temp_output_path(output_path)
194
+ with (
195
+ temp_output_path.open("w", encoding="utf-8") as output_handle,
196
+ sorted_path.open("r", encoding="utf-8") as sorted_handle,
197
+ ):
198
+ for line in sorted_handle:
199
+ text = line.rstrip("\n")
200
+ if not text:
201
+ continue
202
+ key_part, _sequence, record = text.split("\x1f", 2)
203
+ if not key_part:
204
+ continue
205
+ output_handle.write(record)
206
+ output_handle.write("\n")
207
+ os.replace(temp_output_path, output_path)
208
+
209
+
210
+ def _build_sorted_runs(
211
+ input_path: Path,
212
+ *,
213
+ key: SortKey,
214
+ max_records_per_run: int,
215
+ max_bytes_per_run: int,
216
+ temp_root: Path,
217
+ heartbeat: Callable[[], object] | None,
218
+ ) -> list[Path]:
219
+ runs: list[Path] = []
220
+ buffer: list[tuple[str, int, str]] = []
221
+ buffer_bytes = 0
222
+ next_sequence = count(0)
223
+
224
+ def flush() -> None:
225
+ nonlocal buffer, buffer_bytes
226
+ if not buffer:
227
+ return
228
+ buffer.sort(key=lambda item: (item[0], item[1]))
229
+ run_path = temp_root / f".wiki-sort-run-{len(runs):04d}.jsonl"
230
+ with run_path.open("w", encoding="utf-8") as handle:
231
+ for _record_key, _sequence, raw in buffer:
232
+ handle.write(raw)
233
+ handle.write("\n")
234
+ runs.append(run_path)
235
+ buffer = []
236
+ buffer_bytes = 0
237
+ if heartbeat is not None:
238
+ heartbeat()
239
+
240
+ for raw, payload in _iter_records(input_path):
241
+ record_key = _serialize_key(_normalize_key(key(payload)))
242
+ sequence = next(next_sequence)
243
+ line_bytes = len(raw.encode("utf-8")) + len(record_key.encode("utf-8"))
244
+ if buffer and (
245
+ len(buffer) >= max_records_per_run
246
+ or buffer_bytes + line_bytes > max_bytes_per_run
247
+ ):
248
+ flush()
249
+ buffer.append((record_key, sequence, raw))
250
+ buffer_bytes += line_bytes
251
+ flush()
252
+ return runs
253
+
254
+
255
+ def _merge_sorted_runs(
256
+ runs: list[Path],
257
+ *,
258
+ output_path: Path,
259
+ key: SortKey,
260
+ heartbeat: Callable[[], object] | None,
261
+ ) -> None:
262
+ class _RunState:
263
+ __slots__ = ("handle", "index", "line", "path")
264
+
265
+ def __init__(self, path: Path, index: int, handle: TextIO) -> None:
266
+ self.path = path
267
+ self.index = index
268
+ self.handle = handle
269
+ self.line: str | None = None
270
+
271
+ def close_run(state: _RunState) -> None:
272
+ try:
273
+ state.handle.close()
274
+ except OSError:
275
+ pass
276
+ try:
277
+ state.path.unlink()
278
+ except OSError:
279
+ pass
280
+
281
+ entries: list[tuple[str, int, int, _RunState]] = []
282
+ states: list[_RunState] = []
283
+
284
+ def advance(state: _RunState, ordinal: int) -> None:
285
+ if state.line is None:
286
+ state.line = state.handle.readline().rstrip("\n")
287
+ if not state.line:
288
+ return
289
+ payload = json.loads(state.line)
290
+ if not isinstance(payload, dict):
291
+ raise WikiExternalSortError("sorted run contains a non-object record")
292
+ record_key = _serialize_key(_normalize_key(key(payload)))
293
+ heappush(entries, (record_key, state.index, ordinal, state))
294
+
295
+ output_temp = _temp_output_path(output_path)
296
+ order = count(0)
297
+ try:
298
+ for index, run_path in enumerate(runs):
299
+ handle = run_path.open("r", encoding="utf-8")
300
+ state = _RunState(run_path, index, handle)
301
+ states.append(state)
302
+ try:
303
+ advance(state, next(order))
304
+ except Exception:
305
+ close_run(state)
306
+ raise
307
+
308
+ with output_temp.open("w", encoding="utf-8") as output_handle:
309
+ records_written = 0
310
+ while entries:
311
+ _record_key, _run_index, _ordinal, state = heappop(entries)
312
+ if not state.line:
313
+ raise WikiExternalSortError("corrupted merge queue state")
314
+ output_handle.write(state.line)
315
+ output_handle.write("\n")
316
+ records_written += 1
317
+ if heartbeat is not None and records_written % 50_000 == 0:
318
+ heartbeat()
319
+ state.line = None
320
+ try:
321
+ advance(state, next(order))
322
+ except Exception:
323
+ close_run(state)
324
+ raise
325
+
326
+ os.replace(output_temp, output_path)
327
+ finally:
328
+ for state in states:
329
+ close_run(state)
330
+
331
+
332
+ def _iter_records(input_path: Path) -> Iterator[tuple[str, dict[str, Any]]]:
333
+ with input_path.open("r", encoding="utf-8") as handle:
334
+ for line_number, text in enumerate(handle, start=1):
335
+ raw = text.rstrip("\n")
336
+ if raw == "":
337
+ continue
338
+ try:
339
+ payload = json.loads(raw)
340
+ except json.JSONDecodeError as exc:
341
+ raise WikiExternalSortError(
342
+ f"invalid JSON in {input_path} at line {line_number}"
343
+ ) from exc
344
+ if not isinstance(payload, dict):
345
+ raise WikiExternalSortError(
346
+ f"record at line {line_number} in {input_path} is not a JSON object"
347
+ )
348
+ yield raw, payload
349
+
350
+
351
+ def output_with_keys(
352
+ input_path: Path,
353
+ key: SortKey,
354
+ ) -> Iterator[tuple[str, int, str]]:
355
+ for sequence, (raw, payload) in enumerate(_iter_records(input_path)):
356
+ yield _serialize_key(_normalize_key(key(payload))), sequence, raw
357
+
358
+
359
+ def _normalize_key(raw_key: Any) -> tuple[Any, ...]:
360
+ if isinstance(raw_key, tuple):
361
+ return raw_key
362
+ if isinstance(raw_key, list):
363
+ return tuple(raw_key)
364
+ return (raw_key,)
365
+
366
+
367
+ def _serialize_key(value: tuple[Any, ...]) -> str:
368
+ return "\x1e".join(_serialize_key_part(item) for item in value)
369
+
370
+
371
+ def _serialize_key_part(value: Any) -> str:
372
+ if value is None:
373
+ return "n"
374
+ if isinstance(value, bool):
375
+ return "b1" if value else "b0"
376
+ if isinstance(value, int):
377
+ limit = 10**100
378
+ if abs(value) >= limit:
379
+ raise WikiExternalSortError("integer sort key exceeds 100 digits")
380
+ if value < 0:
381
+ return f"i0{limit + value:0101d}"
382
+ return f"i1{value:0101d}"
383
+ if isinstance(value, str):
384
+ return "s" + value.encode("utf-8").hex()
385
+ raise WikiExternalSortError(f"unsupported sort-key component: {value!r}")
386
+
387
+
388
+ def _temp_output_path(output_path: Path) -> Path:
389
+ output_path.parent.mkdir(parents=True, exist_ok=True)
390
+ fd, temp_name = tempfile.mkstemp(prefix=".wiki-sort-", suffix=".tmp", dir=str(output_path.parent))
391
+ os.close(fd)
392
+ return Path(temp_name)