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
delphi_lsp/parser.py ADDED
@@ -0,0 +1,342 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Callable, Iterable
4
+ from dataclasses import dataclass, field
5
+ from functools import lru_cache
6
+ from typing import Any
7
+
8
+ from .comment_builder import build_comment_nodes
9
+ from .consts import AttributeName
10
+ from .grammar import build_grammar
11
+ from .lark_builder import build_syntax_tree
12
+ from .lexical_scanner import scan_brace_comment
13
+ from .nodes import SyntaxNode
14
+ from .parser_backend import (
15
+ ParserBackend,
16
+ ParserMode,
17
+ normalize_backend,
18
+ normalize_mode,
19
+ parse_delphiast_syntax_tree,
20
+ )
21
+ from .preprocessor import (
22
+ IncludeLoader,
23
+ PreprocessedSource,
24
+ Preprocessor,
25
+ PreprocessorOptions,
26
+ )
27
+ from .semantic import SymbolIndex
28
+ from .semantic_builder import SemanticBuilder, SemanticModel
29
+
30
+ StringTransform = Callable[[str], str]
31
+
32
+
33
+ @dataclass
34
+ class ParseResult:
35
+ root: SyntaxNode
36
+ comments: list
37
+ preprocessed: PreprocessedSource
38
+ semantic: SemanticModel | None = None
39
+ problems: list = field(default_factory=list)
40
+
41
+
42
+ class DelphiParseError(Exception):
43
+ """Stable public parse failure independent of the selected parser backend."""
44
+
45
+ def __init__(
46
+ self,
47
+ file_name: str,
48
+ message: str,
49
+ *,
50
+ line: int | None = None,
51
+ column: int | None = None,
52
+ ) -> None:
53
+ self.file_name = file_name
54
+ self.line = line
55
+ self.column = column
56
+ location = file_name
57
+ if line is not None:
58
+ location += f":{line}"
59
+ if column is not None:
60
+ location += f":{column}"
61
+ super().__init__(f"{location}: {message}")
62
+
63
+ @classmethod
64
+ def from_lark(cls, file_name: str, error: BaseException) -> DelphiParseError:
65
+ line = getattr(error, "line", None)
66
+ column = getattr(error, "column", None)
67
+ message = str(error).splitlines()[0].strip()
68
+ if line is not None and column is not None:
69
+ location_suffix = f" at line {line}, column {column}."
70
+ message = message.removesuffix(location_suffix)
71
+ return cls(
72
+ file_name,
73
+ message or type(error).__name__,
74
+ line=line if isinstance(line, int) else None,
75
+ column=column if isinstance(column, int) else None,
76
+ )
77
+
78
+
79
+ class DelphiParser:
80
+ def __init__(
81
+ self,
82
+ *,
83
+ include_paths: Iterable[str] = (),
84
+ defines: Iterable[str] = (),
85
+ include_loader: IncludeLoader | None = None,
86
+ preprocessor_options: PreprocessorOptions | None = None,
87
+ interface_only: bool = False,
88
+ on_handle_string: StringTransform | None = None,
89
+ backend: ParserBackend | str = ParserBackend.DELPHIAST,
90
+ mode: ParserMode | str = ParserMode.STRICT,
91
+ legacy_fallback_max_chars: int = 32 * 1024,
92
+ ) -> None:
93
+ self._include_paths = list(include_paths)
94
+ self._defines = list(defines)
95
+ self._include_loader = include_loader
96
+ self._preprocessor_options = preprocessor_options
97
+ self._interface_only = interface_only
98
+ self._on_handle_string = on_handle_string
99
+ self._backend = normalize_backend(backend)
100
+ self._mode = normalize_mode(mode)
101
+ self._legacy_fallback_max_chars = max(0, legacy_fallback_max_chars)
102
+
103
+ @property
104
+ def backend(self) -> ParserBackend:
105
+ return self._backend
106
+
107
+ @property
108
+ def mode(self) -> ParserMode:
109
+ return self._mode
110
+
111
+ def parse(
112
+ self,
113
+ text: str,
114
+ file_name: str,
115
+ *,
116
+ build_semantic: bool = False,
117
+ index: SymbolIndex | None = None,
118
+ include_loader: IncludeLoader | None = None,
119
+ interface_only: bool | None = None,
120
+ on_handle_string: StringTransform | None = None,
121
+ build_source_map: bool = True,
122
+ build_expressions: bool = True,
123
+ build_node_positions: bool = True,
124
+ build_comments: bool = True,
125
+ ) -> ParseResult:
126
+ effective_include_loader = include_loader if include_loader is not None else self._include_loader
127
+ effective_interface_only = self._interface_only if interface_only is None else interface_only
128
+ effective_string_hook = on_handle_string if on_handle_string is not None else self._on_handle_string
129
+
130
+ preprocessor = Preprocessor(
131
+ defines=self._defines,
132
+ include_paths=self._include_paths,
133
+ include_loader=effective_include_loader,
134
+ options=self._preprocessor_options,
135
+ track_source_map=build_source_map,
136
+ collect_comments=build_comments,
137
+ )
138
+ preprocessed = preprocessor.process(text, file_name)
139
+ parse_text = self._to_interface_only(preprocessed.text) if effective_interface_only else preprocessed.text
140
+ problems: list[Any]
141
+ if self._backend is ParserBackend.LARK:
142
+ tree = self._parse_lark(parse_text, file_name)
143
+ root = self._build_lark_syntax_tree(
144
+ tree,
145
+ file_name,
146
+ string_transform=effective_string_hook,
147
+ )
148
+ problems = []
149
+ else:
150
+ try:
151
+ output = parse_delphiast_syntax_tree(
152
+ parse_text,
153
+ file_name,
154
+ build_expressions=build_expressions,
155
+ build_node_positions=build_node_positions,
156
+ )
157
+ except RecursionError:
158
+ raise DelphiParseError(
159
+ file_name,
160
+ "parser nesting limit exceeded",
161
+ ) from None
162
+ root = output.root
163
+ problems = output.problems
164
+ if (
165
+ self._mode is ParserMode.STRICT
166
+ and len(parse_text) <= self._legacy_fallback_max_chars
167
+ ):
168
+ tree = self._parse_lark(parse_text, file_name)
169
+ root = self._build_lark_syntax_tree(
170
+ tree,
171
+ file_name,
172
+ string_transform=effective_string_hook,
173
+ )
174
+ problems = []
175
+ if effective_string_hook is not None:
176
+ name = root.get_attribute(AttributeName.anName)
177
+ if name:
178
+ root.set_attribute(AttributeName.anName, effective_string_hook(name))
179
+ comments = build_comment_nodes(preprocessed.comments) if build_comments else []
180
+ problems = [*problems, *preprocessed.problems]
181
+ semantic = (
182
+ SemanticBuilder(source_maps={file_name: preprocessed}).build(
183
+ root,
184
+ index=index,
185
+ )
186
+ if build_semantic
187
+ else None
188
+ )
189
+ return ParseResult(
190
+ root=root,
191
+ comments=comments,
192
+ preprocessed=preprocessed,
193
+ semantic=semantic,
194
+ problems=problems,
195
+ )
196
+
197
+ def _parse_lark(self, text: str, file_name: str) -> Any:
198
+ parser = _get_lark_parser()
199
+ try:
200
+ return parser.parse(text)
201
+ except RecursionError:
202
+ raise DelphiParseError(
203
+ file_name,
204
+ "parser nesting limit exceeded",
205
+ ) from None
206
+ except Exception as error:
207
+ if type(error).__module__.partition(".")[0] != "lark":
208
+ raise
209
+ raise DelphiParseError.from_lark(file_name, error) from None
210
+
211
+ def _build_lark_syntax_tree(
212
+ self,
213
+ tree: Any,
214
+ file_name: str,
215
+ *,
216
+ string_transform: StringTransform | None,
217
+ ) -> SyntaxNode:
218
+ try:
219
+ return build_syntax_tree(
220
+ tree,
221
+ file_name,
222
+ string_transform=string_transform,
223
+ )
224
+ except Exception as error:
225
+ nested_error = getattr(error, "orig_exc", error)
226
+ if isinstance(nested_error, RecursionError):
227
+ raise DelphiParseError(
228
+ file_name,
229
+ "parser nesting limit exceeded",
230
+ ) from None
231
+ raise
232
+
233
+ def _to_interface_only(self, text: str) -> str:
234
+ if not text:
235
+ return text
236
+ lower = text.casefold().lstrip()
237
+ if not lower.startswith('unit'):
238
+ return text
239
+
240
+ i = 0
241
+ n = len(text)
242
+ while i < n:
243
+ ch = text[i]
244
+ nxt = text[i + 1] if i + 1 < n else ''
245
+
246
+ if ch == "'" and not (ch == '{' or (ch == '(' and nxt == '*')):
247
+ i += 1
248
+ while i < n:
249
+ if text[i] == "'":
250
+ if i + 1 < n and text[i + 1] == "'":
251
+ i += 2
252
+ continue
253
+ i += 1
254
+ break
255
+ i += 1
256
+ continue
257
+
258
+ if ch == '/' and nxt == '/':
259
+ i += 2
260
+ while i < n and text[i] not in {'\n', '\r'}:
261
+ i += 1
262
+ continue
263
+
264
+ if ch == '{':
265
+ i, _ = scan_brace_comment(text, i)
266
+ continue
267
+
268
+ if ch == '(' and nxt == '*':
269
+ i += 2
270
+ while i + 1 < n and not (text[i] == '*' and text[i + 1] == ')'):
271
+ i += 1
272
+ if i + 1 < n:
273
+ i += 2
274
+ continue
275
+
276
+ if ch.isalpha() or ch == '_':
277
+ start = i
278
+ i += 1
279
+ while i < n and (text[i].isalnum() or text[i] == '_'):
280
+ i += 1
281
+ word = text[start:i]
282
+ if word.casefold() == 'implementation':
283
+ return text[:start] + '\nend.\n'
284
+ continue
285
+
286
+ i += 1
287
+ return text
288
+
289
+
290
+ @lru_cache(maxsize=1)
291
+ def _get_lark_parser():
292
+ try:
293
+ from lark import Lark
294
+ except ImportError as exc: # pragma: no cover - runtime dependency
295
+ raise RuntimeError('lark is required to parse delphi source') from exc
296
+ grammar = build_grammar()
297
+ return Lark(
298
+ grammar,
299
+ parser='lalr',
300
+ lexer='contextual',
301
+ propagate_positions=True,
302
+ maybe_placeholders=False,
303
+ )
304
+
305
+
306
+ def parse(
307
+ text: str,
308
+ file_name: str,
309
+ *,
310
+ include_paths: Iterable[str] = (),
311
+ defines: Iterable[str] = (),
312
+ include_loader: IncludeLoader | None = None,
313
+ preprocessor_options: PreprocessorOptions | None = None,
314
+ build_semantic: bool = False,
315
+ build_source_map: bool = True,
316
+ build_expressions: bool = True,
317
+ build_node_positions: bool = True,
318
+ build_comments: bool = True,
319
+ index: SymbolIndex | None = None,
320
+ interface_only: bool = False,
321
+ on_handle_string: StringTransform | None = None,
322
+ ) -> ParseResult:
323
+ parser = DelphiParser(
324
+ include_paths=include_paths,
325
+ defines=defines,
326
+ include_loader=include_loader,
327
+ preprocessor_options=preprocessor_options,
328
+ interface_only=interface_only,
329
+ on_handle_string=on_handle_string,
330
+ )
331
+ return parser.parse(
332
+ text,
333
+ file_name,
334
+ build_semantic=build_semantic,
335
+ build_source_map=build_source_map,
336
+ build_expressions=build_expressions,
337
+ build_node_positions=build_node_positions,
338
+ build_comments=build_comments,
339
+ index=index,
340
+ interface_only=interface_only,
341
+ on_handle_string=on_handle_string,
342
+ )
@@ -0,0 +1,139 @@
1
+ """Parser backend selection for the DelphiAST Python migration.
2
+
3
+ The DelphiAST-compatible implementation is the default. The legacy Lark
4
+ backend remains available explicitly while compatibility coverage is expanded.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import os
10
+ import platform
11
+ import sys
12
+ from enum import Enum
13
+
14
+ from .delphiast_parser import DelphiAstParseOutput, DelphiAstParser
15
+ from .nodes import SyntaxNode
16
+
17
+
18
+ class ParserBackend(str, Enum):
19
+ DELPHIAST = "delphiast"
20
+ HYBRID = "hybrid"
21
+ LARK = "lark"
22
+
23
+
24
+ class ParserImplementation(str, Enum):
25
+ PYTHON = "python"
26
+ RUST = "rust"
27
+
28
+
29
+ class ParserMode(str, Enum):
30
+ TOLERANT = "tolerant"
31
+ STRICT = "strict"
32
+
33
+
34
+ def normalize_backend(value: ParserBackend | str) -> ParserBackend:
35
+ try:
36
+ return value if isinstance(value, ParserBackend) else ParserBackend(value.casefold())
37
+ except (AttributeError, ValueError) as error:
38
+ raise ValueError(f"Unsupported parser backend: {value!r}.") from error
39
+
40
+
41
+ def normalize_mode(value: ParserMode | str) -> ParserMode:
42
+ try:
43
+ return value if isinstance(value, ParserMode) else ParserMode(value.casefold())
44
+ except (AttributeError, ValueError) as error:
45
+ raise ValueError(f"Unsupported parser mode: {value!r}.") from error
46
+
47
+
48
+ def native_backend_available() -> bool:
49
+ """Return ``True`` when the native Rust parser extension is installed.
50
+
51
+ The import is performed inside the function so that the main package
52
+ never fails on start when the native extension is absent.
53
+ """
54
+ if sys.platform != "win32" or platform.machine().casefold() not in {
55
+ "amd64",
56
+ "x86_64",
57
+ }:
58
+ return False
59
+ try:
60
+ __import__("delphi_lsp.delphi_lsp_native")
61
+ except ImportError:
62
+ return False
63
+ return True
64
+
65
+
66
+ def resolve_parser_implementation() -> ParserImplementation:
67
+ """Decide which implementation to use based on ``DELPHI_LSP_PARSER``.
68
+
69
+ The environment variable is read on every call so that tests can
70
+ ``monkeypatch`` it between calls.
71
+ """
72
+ raw = os.environ.get("DELPHI_LSP_PARSER", "")
73
+ value = raw.casefold() if raw else ""
74
+
75
+ if value == "auto":
76
+ if native_backend_available():
77
+ return ParserImplementation.RUST
78
+ return ParserImplementation.PYTHON
79
+
80
+ if value == "python":
81
+ return ParserImplementation.PYTHON
82
+
83
+ if value == "rust":
84
+ if native_backend_available():
85
+ return ParserImplementation.RUST
86
+ raise RuntimeError(
87
+ "Rust parser backend requested but the native extension is not installed."
88
+ )
89
+
90
+ # Unknown / misspelled value → silently fall back to Python.
91
+ return ParserImplementation.PYTHON
92
+
93
+
94
+ def parse_delphiast_syntax_tree(
95
+ text: str,
96
+ file_name: str,
97
+ *,
98
+ build_expressions: bool = True,
99
+ build_node_positions: bool = True,
100
+ ) -> DelphiAstParseOutput:
101
+ impl = resolve_parser_implementation()
102
+ if impl is ParserImplementation.RUST:
103
+ return _parse_native(text, file_name)
104
+ return DelphiAstParser(
105
+ text,
106
+ file_name,
107
+ build_expressions=build_expressions,
108
+ build_node_positions=build_node_positions,
109
+ ).parse()
110
+
111
+
112
+ def _parse_native(text: str, file_name: str) -> DelphiAstParseOutput:
113
+ """Use the native Rust parser backend and reconstruct SyntaxNode objects."""
114
+ from .delphi_lsp_native import parse_to_serialized # type: ignore[import-not-found]
115
+
116
+ serialized = parse_to_serialized(text, file_name)
117
+ from .native_bridge import reconstruct_syntax_tree
118
+
119
+ root = reconstruct_syntax_tree(serialized)
120
+ return DelphiAstParseOutput(root=root, problems=[])
121
+
122
+
123
+ def build_delphiast_syntax_tree(text: str, file_name: str) -> SyntaxNode:
124
+ """Compatibility helper for callers that only need the root node."""
125
+
126
+ return parse_delphiast_syntax_tree(text, file_name).root
127
+
128
+
129
+ __all__ = [
130
+ "ParserBackend",
131
+ "ParserImplementation",
132
+ "ParserMode",
133
+ "build_delphiast_syntax_tree",
134
+ "native_backend_available",
135
+ "normalize_backend",
136
+ "normalize_mode",
137
+ "parse_delphiast_syntax_tree",
138
+ "resolve_parser_implementation",
139
+ ]