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.
- delphi_lsp/__init__.py +184 -0
- delphi_lsp/_version.py +1 -0
- delphi_lsp/agent_cache.py +1574 -0
- delphi_lsp/agent_cli.py +902 -0
- delphi_lsp/agent_context.py +3907 -0
- delphi_lsp/agent_cpg.py +349 -0
- delphi_lsp/agent_cpg_builder.py +666 -0
- delphi_lsp/agent_layers.py +1410 -0
- delphi_lsp/agent_metrics.py +526 -0
- delphi_lsp/agent_protocol.py +445 -0
- delphi_lsp/agent_relations.py +1881 -0
- delphi_lsp/agent_templates.py +614 -0
- delphi_lsp/agent_wiki.py +2853 -0
- delphi_lsp/agent_workspace.py +860 -0
- delphi_lsp/ast_serialize.py +126 -0
- delphi_lsp/binary.py +250 -0
- delphi_lsp/comment_builder.py +28 -0
- delphi_lsp/consts.py +345 -0
- delphi_lsp/delphi_lsp_native.pyd +0 -0
- delphi_lsp/delphiast_lexer.py +399 -0
- delphi_lsp/delphiast_parser.py +2189 -0
- delphi_lsp/delphiast_tokens.py +41 -0
- delphi_lsp/grammar.py +459 -0
- delphi_lsp/incremental.py +1139 -0
- delphi_lsp/lark_builder.py +2684 -0
- delphi_lsp/lark_tokens.py +236 -0
- delphi_lsp/lexical_scanner.py +30 -0
- delphi_lsp/lsp_server.py +6698 -0
- delphi_lsp/metrics.py +1452 -0
- delphi_lsp/native_bridge.py +300 -0
- delphi_lsp/navigation_cache.py +189 -0
- delphi_lsp/nodes.py +462 -0
- delphi_lsp/parallel_outline.py +662 -0
- delphi_lsp/parse_artifacts.py +969 -0
- delphi_lsp/parser.py +342 -0
- delphi_lsp/parser_backend.py +139 -0
- delphi_lsp/preprocessor.py +1122 -0
- delphi_lsp/progress.py +26 -0
- delphi_lsp/project_config.py +285 -0
- delphi_lsp/project_discovery.py +932 -0
- delphi_lsp/project_indexer.py +470 -0
- delphi_lsp/py.typed +1 -0
- delphi_lsp/response_cache.py +465 -0
- delphi_lsp/semantic.py +394 -0
- delphi_lsp/semantic_builder.py +2302 -0
- delphi_lsp/source_reader.py +17 -0
- delphi_lsp/wiki_external_sort.py +392 -0
- delphi_lsp/wiki_memory.py +548 -0
- delphi_lsp/workspace.py +83 -0
- delphi_lsp/writer.py +73 -0
- python_delphi_lsp-3.6.0.dist-info/METADATA +766 -0
- python_delphi_lsp-3.6.0.dist-info/RECORD +56 -0
- python_delphi_lsp-3.6.0.dist-info/WHEEL +4 -0
- python_delphi_lsp-3.6.0.dist-info/entry_points.txt +3 -0
- python_delphi_lsp-3.6.0.dist-info/licenses/LICENSE +373 -0
- 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)
|