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
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
|
+
]
|