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.
- agent_codinglanguage_mapper/__init__.py +20 -0
- agent_codinglanguage_mapper/__main__.py +4 -0
- agent_codinglanguage_mapper/_version.py +2 -0
- agent_codinglanguage_mapper/adapters/__init__.py +4 -0
- agent_codinglanguage_mapper/adapters/delphi.py +822 -0
- agent_codinglanguage_mapper/adapters/delphi_frontend/__init__.py +6 -0
- agent_codinglanguage_mapper/adapters/delphi_frontend/comment_builder.py +29 -0
- agent_codinglanguage_mapper/adapters/delphi_frontend/consts.py +345 -0
- agent_codinglanguage_mapper/adapters/delphi_frontend/grammar.py +460 -0
- agent_codinglanguage_mapper/adapters/delphi_frontend/lark_builder.py +2674 -0
- agent_codinglanguage_mapper/adapters/delphi_frontend/lark_tokens.py +237 -0
- agent_codinglanguage_mapper/adapters/delphi_frontend/lsp_server.py +1832 -0
- agent_codinglanguage_mapper/adapters/delphi_frontend/nodes.py +371 -0
- agent_codinglanguage_mapper/adapters/delphi_frontend/parser.py +193 -0
- agent_codinglanguage_mapper/adapters/delphi_frontend/preprocessor.py +997 -0
- agent_codinglanguage_mapper/adapters/delphi_frontend/project_discovery.py +518 -0
- agent_codinglanguage_mapper/adapters/delphi_frontend/project_indexer.py +319 -0
- agent_codinglanguage_mapper/adapters/delphi_frontend/semantic.py +375 -0
- agent_codinglanguage_mapper/adapters/delphi_frontend/semantic_builder.py +1384 -0
- agent_codinglanguage_mapper/adapters/delphi_frontend/source_reader.py +17 -0
- agent_codinglanguage_mapper/adapters/delphi_frontend/workspace.py +67 -0
- agent_codinglanguage_mapper/adapters/tree_sitter.py +1722 -0
- agent_codinglanguage_mapper/cli.py +138 -0
- agent_codinglanguage_mapper/discovery.py +1328 -0
- agent_codinglanguage_mapper/indexer.py +1102 -0
- agent_codinglanguage_mapper/integration.py +186 -0
- agent_codinglanguage_mapper/lsp_server.py +413 -0
- agent_codinglanguage_mapper/lsp_service.py +447 -0
- agent_codinglanguage_mapper/mapper.py +596 -0
- agent_codinglanguage_mapper/models.py +101 -0
- agent_codinglanguage_mapper/protocol.py +152 -0
- agent_codinglanguage_mapper/rendering.py +153 -0
- agent_codinglanguage_mapper/templates/opencode/plugins/codebase_map.ts +191 -0
- agent_codinglanguage_mapper/templates/skill/SKILL.md +52 -0
- agent_codinglanguage_mapper/worker.py +29 -0
- agent_codinglanguage_mapper/workspace.py +114 -0
- agent_codinglanguage_mapper-1.0.0.dist-info/METADATA +383 -0
- agent_codinglanguage_mapper-1.0.0.dist-info/RECORD +42 -0
- agent_codinglanguage_mapper-1.0.0.dist-info/WHEEL +5 -0
- agent_codinglanguage_mapper-1.0.0.dist-info/entry_points.txt +2 -0
- agent_codinglanguage_mapper-1.0.0.dist-info/licenses/LICENSE +373 -0
- 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
|
+
)
|