agent-codinglanguage-mapper 1.0.0__py3-none-any.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 (42) hide show
  1. agent_codinglanguage_mapper/__init__.py +20 -0
  2. agent_codinglanguage_mapper/__main__.py +4 -0
  3. agent_codinglanguage_mapper/_version.py +2 -0
  4. agent_codinglanguage_mapper/adapters/__init__.py +4 -0
  5. agent_codinglanguage_mapper/adapters/delphi.py +822 -0
  6. agent_codinglanguage_mapper/adapters/delphi_frontend/__init__.py +6 -0
  7. agent_codinglanguage_mapper/adapters/delphi_frontend/comment_builder.py +29 -0
  8. agent_codinglanguage_mapper/adapters/delphi_frontend/consts.py +345 -0
  9. agent_codinglanguage_mapper/adapters/delphi_frontend/grammar.py +460 -0
  10. agent_codinglanguage_mapper/adapters/delphi_frontend/lark_builder.py +2674 -0
  11. agent_codinglanguage_mapper/adapters/delphi_frontend/lark_tokens.py +237 -0
  12. agent_codinglanguage_mapper/adapters/delphi_frontend/lsp_server.py +1832 -0
  13. agent_codinglanguage_mapper/adapters/delphi_frontend/nodes.py +371 -0
  14. agent_codinglanguage_mapper/adapters/delphi_frontend/parser.py +193 -0
  15. agent_codinglanguage_mapper/adapters/delphi_frontend/preprocessor.py +997 -0
  16. agent_codinglanguage_mapper/adapters/delphi_frontend/project_discovery.py +518 -0
  17. agent_codinglanguage_mapper/adapters/delphi_frontend/project_indexer.py +319 -0
  18. agent_codinglanguage_mapper/adapters/delphi_frontend/semantic.py +375 -0
  19. agent_codinglanguage_mapper/adapters/delphi_frontend/semantic_builder.py +1384 -0
  20. agent_codinglanguage_mapper/adapters/delphi_frontend/source_reader.py +17 -0
  21. agent_codinglanguage_mapper/adapters/delphi_frontend/workspace.py +67 -0
  22. agent_codinglanguage_mapper/adapters/tree_sitter.py +1722 -0
  23. agent_codinglanguage_mapper/cli.py +138 -0
  24. agent_codinglanguage_mapper/discovery.py +1328 -0
  25. agent_codinglanguage_mapper/indexer.py +1102 -0
  26. agent_codinglanguage_mapper/integration.py +186 -0
  27. agent_codinglanguage_mapper/lsp_server.py +413 -0
  28. agent_codinglanguage_mapper/lsp_service.py +447 -0
  29. agent_codinglanguage_mapper/mapper.py +596 -0
  30. agent_codinglanguage_mapper/models.py +101 -0
  31. agent_codinglanguage_mapper/protocol.py +152 -0
  32. agent_codinglanguage_mapper/rendering.py +153 -0
  33. agent_codinglanguage_mapper/templates/opencode/plugins/codebase_map.ts +191 -0
  34. agent_codinglanguage_mapper/templates/skill/SKILL.md +52 -0
  35. agent_codinglanguage_mapper/worker.py +29 -0
  36. agent_codinglanguage_mapper/workspace.py +114 -0
  37. agent_codinglanguage_mapper-1.0.0.dist-info/METADATA +383 -0
  38. agent_codinglanguage_mapper-1.0.0.dist-info/RECORD +42 -0
  39. agent_codinglanguage_mapper-1.0.0.dist-info/WHEEL +5 -0
  40. agent_codinglanguage_mapper-1.0.0.dist-info/entry_points.txt +2 -0
  41. agent_codinglanguage_mapper-1.0.0.dist-info/licenses/LICENSE +373 -0
  42. agent_codinglanguage_mapper-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,371 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from enum import Enum
5
+ from typing import Iterable, Optional, Sequence
6
+
7
+ from .consts import AttributeName, SyntaxNodeType
8
+
9
+
10
+ class ParserException(Exception):
11
+ def __init__(self, line: int, col: int, file_name: str, message: str) -> None:
12
+ super().__init__(message)
13
+ self.file_name = file_name
14
+ self.line = line
15
+ self.col = col
16
+
17
+
18
+ class SyntaxTreeException(ParserException):
19
+ def __init__(self, line: int, col: int, file_name: str, message: str, syntax_tree: 'SyntaxNode') -> None:
20
+ super().__init__(line, col, file_name, message)
21
+ self.syntax_tree = syntax_tree
22
+
23
+
24
+ class SyntaxNode:
25
+ def __init__(self, typ: SyntaxNodeType) -> None:
26
+ self._typ = typ
27
+ self._col = 0
28
+ self._line = 0
29
+ self._file_name = ''
30
+ self._attributes: list[tuple[AttributeName, str]] = []
31
+ self._child_nodes: list['SyntaxNode'] = []
32
+ self._parent_node: Optional['SyntaxNode'] = None
33
+
34
+ @property
35
+ def typ(self) -> SyntaxNodeType:
36
+ return self._typ
37
+
38
+ @property
39
+ def col(self) -> int:
40
+ return self._col
41
+
42
+ @col.setter
43
+ def col(self, value: int) -> None:
44
+ self._col = value
45
+
46
+ @property
47
+ def line(self) -> int:
48
+ return self._line
49
+
50
+ @line.setter
51
+ def line(self, value: int) -> None:
52
+ self._line = value
53
+
54
+ @property
55
+ def file_name(self) -> str:
56
+ return self._file_name
57
+
58
+ @file_name.setter
59
+ def file_name(self, value: str) -> None:
60
+ self._file_name = value
61
+
62
+ @property
63
+ def attributes(self) -> list[tuple[AttributeName, str]]:
64
+ return self._attributes
65
+
66
+ @property
67
+ def child_nodes(self) -> list['SyntaxNode']:
68
+ return self._child_nodes
69
+
70
+ @property
71
+ def has_attributes(self) -> bool:
72
+ return len(self._attributes) > 0
73
+
74
+ @property
75
+ def has_children(self) -> bool:
76
+ return len(self._child_nodes) > 0
77
+
78
+ @property
79
+ def parent_node(self) -> Optional['SyntaxNode']:
80
+ return self._parent_node
81
+
82
+ def assign_position_from(self, node: 'SyntaxNode') -> None:
83
+ self._col = node.col
84
+ self._line = node.line
85
+ self._file_name = node.file_name
86
+
87
+ def clone(self) -> 'SyntaxNode':
88
+ clone_node = self.__class__(self._typ)
89
+ clone_node._col = self._col
90
+ clone_node._line = self._line
91
+ clone_node._file_name = self._file_name
92
+ clone_node._attributes = list(self._attributes)
93
+ clone_node._child_nodes = [child.clone() for child in self._child_nodes]
94
+ for child in clone_node._child_nodes:
95
+ child._parent_node = clone_node
96
+ return clone_node
97
+
98
+ def get_attribute(self, key: AttributeName) -> str:
99
+ for attr_key, attr_value in self._attributes:
100
+ if attr_key == key:
101
+ return attr_value
102
+ return ''
103
+
104
+ def has_attribute(self, key: AttributeName) -> bool:
105
+ return any(attr_key == key for attr_key, _ in self._attributes)
106
+
107
+ def set_attribute(self, key: AttributeName, value: str) -> None:
108
+ for index, (attr_key, _) in enumerate(self._attributes):
109
+ if attr_key == key:
110
+ self._attributes[index] = (key, value)
111
+ return
112
+ self._attributes.append((key, value))
113
+
114
+ def clear_attributes(self) -> None:
115
+ self._attributes = []
116
+
117
+ def add_child(self, node_or_type: 'SyntaxNode | SyntaxNodeType') -> 'SyntaxNode':
118
+ if isinstance(node_or_type, SyntaxNodeType):
119
+ node = SyntaxNode(node_or_type)
120
+ else:
121
+ node = node_or_type
122
+ if node is None:
123
+ raise ValueError('node must not be None')
124
+ self._child_nodes.append(node)
125
+ node._parent_node = self
126
+ return node
127
+
128
+ def extract_child(self, node: 'SyntaxNode') -> None:
129
+ try:
130
+ index = self._child_nodes.index(node)
131
+ except ValueError:
132
+ return
133
+ self._child_nodes.pop(index)
134
+ node._parent_node = None
135
+
136
+ def delete_child(self, node: 'SyntaxNode') -> None:
137
+ self.extract_child(node)
138
+
139
+ def find_node(self, typ_or_path: SyntaxNodeType | Sequence[SyntaxNodeType]) -> Optional['SyntaxNode']:
140
+ if isinstance(typ_or_path, SyntaxNodeType):
141
+ for child in self._child_nodes:
142
+ if child.typ == typ_or_path:
143
+ return child
144
+ return None
145
+
146
+ types_path = list(typ_or_path)
147
+ if not types_path:
148
+ return None
149
+ if types_path[-1] == SyntaxNodeType.ntUnknown:
150
+ return None
151
+ return self._find_node_recursively(self, types_path, 0)
152
+
153
+ def _find_node_recursively(
154
+ self,
155
+ node: 'SyntaxNode',
156
+ types_path: Sequence[SyntaxNodeType],
157
+ type_index: int,
158
+ ) -> Optional['SyntaxNode']:
159
+ for child in node.child_nodes:
160
+ if types_path[type_index] in (child.typ, SyntaxNodeType.ntUnknown):
161
+ if type_index < len(types_path) - 1:
162
+ found = self._find_node_recursively(child, types_path, type_index + 1)
163
+ else:
164
+ found = child
165
+ if found is not None:
166
+ return found
167
+ return None
168
+
169
+
170
+ class CompoundSyntaxNode(SyntaxNode):
171
+ def __init__(self, typ: SyntaxNodeType) -> None:
172
+ super().__init__(typ)
173
+ self.end_col = 0
174
+ self.end_line = 0
175
+
176
+ def clone(self) -> 'SyntaxNode':
177
+ clone_node = super().clone()
178
+ clone_node.end_col = self.end_col
179
+ clone_node.end_line = self.end_line
180
+ return clone_node
181
+
182
+
183
+ class ValuedSyntaxNode(SyntaxNode):
184
+ def __init__(self, typ: SyntaxNodeType) -> None:
185
+ super().__init__(typ)
186
+ self.value = ''
187
+
188
+ def clone(self) -> 'SyntaxNode':
189
+ clone_node = super().clone()
190
+ clone_node.value = self.value
191
+ return clone_node
192
+
193
+
194
+ class CommentNode(SyntaxNode):
195
+ def __init__(self, typ: SyntaxNodeType) -> None:
196
+ super().__init__(typ)
197
+ self.text = ''
198
+
199
+ def clone(self) -> 'SyntaxNode':
200
+ clone_node = super().clone()
201
+ clone_node.text = self.text
202
+ return clone_node
203
+
204
+
205
+ class OperatorKind(Enum):
206
+ UNARY = 'unary'
207
+ BINARY = 'binary'
208
+
209
+
210
+ class OperatorAssoc(Enum):
211
+ LEFT = 'left'
212
+ RIGHT = 'right'
213
+
214
+
215
+ @dataclass(frozen=True)
216
+ class OperatorInfo:
217
+ typ: SyntaxNodeType
218
+ priority: int
219
+ kind: OperatorKind
220
+ assoc: OperatorAssoc
221
+
222
+
223
+ _OPERATOR_INFO = (
224
+ OperatorInfo(SyntaxNodeType.ntAddr, 1, OperatorKind.UNARY, OperatorAssoc.RIGHT),
225
+ OperatorInfo(SyntaxNodeType.ntDeref, 1, OperatorKind.UNARY, OperatorAssoc.LEFT),
226
+ OperatorInfo(SyntaxNodeType.ntGeneric, 1, OperatorKind.BINARY, OperatorAssoc.RIGHT),
227
+ OperatorInfo(SyntaxNodeType.ntIndexed, 1, OperatorKind.UNARY, OperatorAssoc.LEFT),
228
+ OperatorInfo(SyntaxNodeType.ntDot, 2, OperatorKind.BINARY, OperatorAssoc.RIGHT),
229
+ OperatorInfo(SyntaxNodeType.ntCall, 3, OperatorKind.BINARY, OperatorAssoc.RIGHT),
230
+ OperatorInfo(SyntaxNodeType.ntUnaryMinus, 5, OperatorKind.UNARY, OperatorAssoc.RIGHT),
231
+ OperatorInfo(SyntaxNodeType.ntNot, 6, OperatorKind.UNARY, OperatorAssoc.RIGHT),
232
+ OperatorInfo(SyntaxNodeType.ntMul, 7, OperatorKind.BINARY, OperatorAssoc.RIGHT),
233
+ OperatorInfo(SyntaxNodeType.ntFDiv, 7, OperatorKind.BINARY, OperatorAssoc.RIGHT),
234
+ OperatorInfo(SyntaxNodeType.ntDiv, 7, OperatorKind.BINARY, OperatorAssoc.RIGHT),
235
+ OperatorInfo(SyntaxNodeType.ntMod, 7, OperatorKind.BINARY, OperatorAssoc.RIGHT),
236
+ OperatorInfo(SyntaxNodeType.ntAnd, 7, OperatorKind.BINARY, OperatorAssoc.RIGHT),
237
+ OperatorInfo(SyntaxNodeType.ntShl, 7, OperatorKind.BINARY, OperatorAssoc.RIGHT),
238
+ OperatorInfo(SyntaxNodeType.ntShr, 7, OperatorKind.BINARY, OperatorAssoc.RIGHT),
239
+ OperatorInfo(SyntaxNodeType.ntAs, 7, OperatorKind.BINARY, OperatorAssoc.RIGHT),
240
+ OperatorInfo(SyntaxNodeType.ntAdd, 8, OperatorKind.BINARY, OperatorAssoc.RIGHT),
241
+ OperatorInfo(SyntaxNodeType.ntSub, 8, OperatorKind.BINARY, OperatorAssoc.RIGHT),
242
+ OperatorInfo(SyntaxNodeType.ntOr, 8, OperatorKind.BINARY, OperatorAssoc.RIGHT),
243
+ OperatorInfo(SyntaxNodeType.ntXor, 8, OperatorKind.BINARY, OperatorAssoc.RIGHT),
244
+ OperatorInfo(SyntaxNodeType.ntEqual, 9, OperatorKind.BINARY, OperatorAssoc.RIGHT),
245
+ OperatorInfo(SyntaxNodeType.ntNotEqual, 9, OperatorKind.BINARY, OperatorAssoc.RIGHT),
246
+ OperatorInfo(SyntaxNodeType.ntLower, 9, OperatorKind.BINARY, OperatorAssoc.RIGHT),
247
+ OperatorInfo(SyntaxNodeType.ntGreater, 9, OperatorKind.BINARY, OperatorAssoc.RIGHT),
248
+ OperatorInfo(SyntaxNodeType.ntLowerEqual, 9, OperatorKind.BINARY, OperatorAssoc.RIGHT),
249
+ OperatorInfo(SyntaxNodeType.ntGreaterEqual, 9, OperatorKind.BINARY, OperatorAssoc.RIGHT),
250
+ OperatorInfo(SyntaxNodeType.ntIn, 9, OperatorKind.BINARY, OperatorAssoc.RIGHT),
251
+ OperatorInfo(SyntaxNodeType.ntIs, 9, OperatorKind.BINARY, OperatorAssoc.RIGHT),
252
+ OperatorInfo(SyntaxNodeType.ntNotIn, 9, OperatorKind.BINARY, OperatorAssoc.RIGHT),
253
+ OperatorInfo(SyntaxNodeType.ntIsNot, 9, OperatorKind.BINARY, OperatorAssoc.RIGHT),
254
+ )
255
+
256
+ _OPERATORS = {info.typ: info for info in _OPERATOR_INFO}
257
+
258
+
259
+ def _is_round_close(typ: SyntaxNodeType) -> bool:
260
+ return typ == SyntaxNodeType.ntRoundClose
261
+
262
+
263
+ def _is_round_open(typ: SyntaxNodeType) -> bool:
264
+ return typ == SyntaxNodeType.ntRoundOpen
265
+
266
+
267
+ def is_operator(typ: SyntaxNodeType) -> bool:
268
+ return typ in _OPERATORS
269
+
270
+
271
+ def operator_info(typ: SyntaxNodeType) -> OperatorInfo:
272
+ return _OPERATORS[typ]
273
+
274
+
275
+ def expr_to_reverse_notation(expr: Iterable[SyntaxNode]) -> list[SyntaxNode]:
276
+ output: list[SyntaxNode] = []
277
+ stack: list[SyntaxNode] = []
278
+ for node in expr:
279
+ if is_operator(node.typ):
280
+ while stack and is_operator(stack[-1].typ):
281
+ current = operator_info(node.typ)
282
+ top = operator_info(stack[-1].typ)
283
+ if (
284
+ (current.assoc == OperatorAssoc.LEFT and current.priority >= top.priority)
285
+ or (current.assoc == OperatorAssoc.RIGHT and current.priority > top.priority)
286
+ ):
287
+ output.append(stack.pop())
288
+ else:
289
+ break
290
+ stack.append(node)
291
+ elif _is_round_open(node.typ):
292
+ stack.append(node)
293
+ elif _is_round_close(node.typ):
294
+ while stack and not _is_round_open(stack[-1].typ):
295
+ output.append(stack.pop())
296
+ if stack:
297
+ stack.pop()
298
+ if stack and is_operator(stack[-1].typ):
299
+ output.append(stack.pop())
300
+ else:
301
+ output.append(node)
302
+ while stack:
303
+ output.append(stack.pop())
304
+ return output
305
+
306
+
307
+ def node_list_to_tree(expr: Iterable[SyntaxNode], root: SyntaxNode) -> None:
308
+ stack: list[SyntaxNode] = []
309
+ for node in expr:
310
+ if is_operator(node.typ):
311
+ info = operator_info(node.typ)
312
+ if info.kind == OperatorKind.UNARY:
313
+ node.add_child(stack.pop())
314
+ else:
315
+ second = stack.pop()
316
+ node.add_child(stack.pop())
317
+ node.add_child(second)
318
+ stack.append(node)
319
+ root.add_child(stack.pop())
320
+ if stack:
321
+ raise ValueError('expression stack not empty after parsing')
322
+
323
+
324
+ def prepare_expr(expr_nodes: Iterable[SyntaxNode]) -> list[SyntaxNode]:
325
+ prepared: list[SyntaxNode] = []
326
+ prev_node: Optional[SyntaxNode] = None
327
+ for node in expr_nodes:
328
+ if node.typ == SyntaxNodeType.ntCall:
329
+ continue
330
+
331
+ if prev_node is not None and _is_round_open(node.typ):
332
+ if not is_operator(prev_node.typ) and not _is_round_open(prev_node.typ):
333
+ prepared.append(create_node_with_parents_position(SyntaxNodeType.ntCall, node.parent_node))
334
+ if (
335
+ is_operator(prev_node.typ)
336
+ and operator_info(prev_node.typ).kind == OperatorKind.UNARY
337
+ and operator_info(prev_node.typ).assoc == OperatorAssoc.LEFT
338
+ ):
339
+ prepared.append(create_node_with_parents_position(SyntaxNodeType.ntCall, node.parent_node))
340
+
341
+ if prev_node is not None and node.typ == SyntaxNodeType.ntTypeArgs:
342
+ if not is_operator(prev_node.typ) and prev_node.typ != SyntaxNodeType.ntTypeArgs:
343
+ prepared.append(create_node_with_parents_position(SyntaxNodeType.ntGeneric, node.parent_node))
344
+ if (
345
+ is_operator(prev_node.typ)
346
+ and operator_info(prev_node.typ).kind == OperatorKind.UNARY
347
+ and operator_info(prev_node.typ).assoc == OperatorAssoc.LEFT
348
+ ):
349
+ prepared.append(create_node_with_parents_position(SyntaxNodeType.ntGeneric, node.parent_node))
350
+
351
+ if node.typ != SyntaxNodeType.ntAlignmentParam:
352
+ prepared.append(node.clone())
353
+ prev_node = node
354
+
355
+ return prepared
356
+
357
+
358
+ def create_node_with_parents_position(node_type: SyntaxNodeType, parent_node: Optional[SyntaxNode]) -> SyntaxNode:
359
+ node = SyntaxNode(node_type)
360
+ if parent_node is not None:
361
+ node.assign_position_from(parent_node)
362
+ return node
363
+
364
+
365
+ def raw_node_list_to_tree(raw_parent_node: SyntaxNode, raw_node_list: Iterable[SyntaxNode], new_root: SyntaxNode) -> None:
366
+ try:
367
+ prepared = prepare_expr(raw_node_list)
368
+ reverse = expr_to_reverse_notation(prepared)
369
+ node_list_to_tree(reverse, new_root)
370
+ except Exception as exc: # pragma: no cover - parity with Pascal error handling
371
+ raise ParserException(new_root.line, new_root.col, new_root.file_name, str(exc)) from exc
@@ -0,0 +1,193 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from functools import lru_cache
5
+ from pathlib import Path
6
+ from typing import Callable, Iterable, Optional
7
+
8
+ from .comment_builder import build_comment_nodes
9
+ from .grammar import build_grammar
10
+ from .lark_builder import build_syntax_tree
11
+ from .nodes import SyntaxNode
12
+ from .preprocessor import IncludeLoader, PreprocessedSource, Preprocessor, PreprocessorOptions
13
+ from .semantic import SymbolIndex
14
+ from .semantic_builder import SemanticBuilder, SemanticModel
15
+
16
+ StringTransform = Callable[[str], str]
17
+
18
+
19
+ @dataclass
20
+ class ParseResult:
21
+ root: SyntaxNode
22
+ comments: list
23
+ preprocessed: PreprocessedSource
24
+ semantic: Optional[SemanticModel] = None
25
+
26
+
27
+ class DelphiParser:
28
+ def __init__(
29
+ self,
30
+ *,
31
+ include_paths: Iterable[str] = (),
32
+ defines: Iterable[str] = (),
33
+ include_loader: IncludeLoader | None = None,
34
+ workspace_root: str | Path | None = None,
35
+ preprocessor_options: Optional[PreprocessorOptions] = None,
36
+ interface_only: bool = False,
37
+ on_handle_string: StringTransform | None = None,
38
+ ) -> None:
39
+ self._include_paths = list(include_paths)
40
+ self._defines = list(defines)
41
+ self._include_loader = include_loader
42
+ self._workspace_root = workspace_root
43
+ self._preprocessor_options = preprocessor_options
44
+ self._interface_only = interface_only
45
+ self._on_handle_string = on_handle_string
46
+
47
+ def parse(
48
+ self,
49
+ text: str,
50
+ file_name: str,
51
+ *,
52
+ build_semantic: bool = False,
53
+ index: SymbolIndex | None = None,
54
+ include_loader: IncludeLoader | None = None,
55
+ interface_only: bool | None = None,
56
+ on_handle_string: StringTransform | None = None,
57
+ preprocessed_source: PreprocessedSource | None = None,
58
+ ) -> ParseResult:
59
+ effective_include_loader = include_loader if include_loader is not None else self._include_loader
60
+ effective_interface_only = self._interface_only if interface_only is None else interface_only
61
+ effective_string_hook = on_handle_string if on_handle_string is not None else self._on_handle_string
62
+
63
+ preprocessed = preprocessed_source
64
+ if preprocessed is None:
65
+ preprocessor = Preprocessor(
66
+ defines=self._defines,
67
+ include_paths=self._include_paths,
68
+ include_loader=effective_include_loader,
69
+ workspace_root=self._workspace_root,
70
+ options=self._preprocessor_options,
71
+ )
72
+ preprocessed = preprocessor.process(text, file_name)
73
+ parse_text = self._to_interface_only(preprocessed.text) if effective_interface_only else preprocessed.text
74
+ tree = self._parse_lark(parse_text)
75
+ root = build_syntax_tree(tree, file_name, string_transform=effective_string_hook)
76
+ comments = build_comment_nodes(preprocessed.comments)
77
+ semantic = SemanticBuilder().build(root, index=index) if build_semantic else None
78
+ return ParseResult(root=root, comments=comments, preprocessed=preprocessed, semantic=semantic)
79
+
80
+ def _parse_lark(self, text: str):
81
+ parser = _get_lark_parser()
82
+ return parser.parse(text)
83
+
84
+ def _to_interface_only(self, text: str) -> str:
85
+ if not text:
86
+ return text
87
+ lower = text.casefold().lstrip()
88
+ if not lower.startswith('unit'):
89
+ return text
90
+
91
+ i = 0
92
+ n = len(text)
93
+ while i < n:
94
+ ch = text[i]
95
+ nxt = text[i + 1] if i + 1 < n else ''
96
+
97
+ if ch == "'" and not (ch == '{' or (ch == '(' and nxt == '*')):
98
+ i += 1
99
+ while i < n:
100
+ if text[i] == "'":
101
+ if i + 1 < n and text[i + 1] == "'":
102
+ i += 2
103
+ continue
104
+ i += 1
105
+ break
106
+ i += 1
107
+ continue
108
+
109
+ if ch == '/' and nxt == '/':
110
+ i += 2
111
+ while i < n and text[i] not in {'\n', '\r'}:
112
+ i += 1
113
+ continue
114
+
115
+ if ch == '{':
116
+ i += 1
117
+ while i < n and text[i] != '}':
118
+ i += 1
119
+ if i < n:
120
+ i += 1
121
+ continue
122
+
123
+ if ch == '(' and nxt == '*':
124
+ i += 2
125
+ while i + 1 < n and not (text[i] == '*' and text[i + 1] == ')'):
126
+ i += 1
127
+ if i + 1 < n:
128
+ i += 2
129
+ continue
130
+
131
+ if ch.isalpha() or ch == '_':
132
+ start = i
133
+ i += 1
134
+ while i < n and (text[i].isalnum() or text[i] == '_'):
135
+ i += 1
136
+ word = text[start:i]
137
+ if word.casefold() == 'implementation':
138
+ return text[:start] + '\nend.\n'
139
+ continue
140
+
141
+ i += 1
142
+ return text
143
+
144
+
145
+ @lru_cache(maxsize=1)
146
+ def _get_lark_parser():
147
+ try:
148
+ from lark import Lark
149
+ except ImportError as exc: # pragma: no cover - runtime dependency
150
+ raise RuntimeError('lark is required to parse delphi source') from exc
151
+ grammar = build_grammar()
152
+ return Lark(
153
+ grammar,
154
+ parser='lalr',
155
+ lexer='contextual',
156
+ propagate_positions=True,
157
+ maybe_placeholders=False,
158
+ )
159
+
160
+
161
+ def parse(
162
+ text: str,
163
+ file_name: str,
164
+ *,
165
+ include_paths: Iterable[str] = (),
166
+ defines: Iterable[str] = (),
167
+ include_loader: IncludeLoader | None = None,
168
+ workspace_root: str | Path | None = None,
169
+ preprocessor_options: Optional[PreprocessorOptions] = None,
170
+ build_semantic: bool = False,
171
+ index: SymbolIndex | None = None,
172
+ interface_only: bool = False,
173
+ on_handle_string: StringTransform | None = None,
174
+ preprocessed_source: PreprocessedSource | None = None,
175
+ ) -> ParseResult:
176
+ parser = DelphiParser(
177
+ include_paths=include_paths,
178
+ defines=defines,
179
+ include_loader=include_loader,
180
+ workspace_root=workspace_root,
181
+ preprocessor_options=preprocessor_options,
182
+ interface_only=interface_only,
183
+ on_handle_string=on_handle_string,
184
+ preprocessed_source=preprocessed_source,
185
+ )
186
+ return parser.parse(
187
+ text,
188
+ file_name,
189
+ build_semantic=build_semantic,
190
+ index=index,
191
+ interface_only=interface_only,
192
+ on_handle_string=on_handle_string,
193
+ )