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
@@ -0,0 +1,126 @@
1
+ """Canonical serialization of a SyntaxNode tree.
2
+
3
+ Produces a deterministic text representation so that two parses of the same
4
+ source always yield identical output — ideal for diff-based verification of
5
+ a native (Rust) parser against the Python reference implementation.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from .consts import ATTRIBUTE_NAME_STRINGS, SyntaxNodeType
11
+ from .nodes import SyntaxNode
12
+
13
+
14
+ def _escape_value(value: str) -> str:
15
+ """Escape a node-value so it never contains unescaped newlines or tabs."""
16
+ return (
17
+ value
18
+ .replace("\\", "\\\\")
19
+ .replace("\n", "\\n")
20
+ .replace("\r", "\\r")
21
+ .replace("\t", "\\t")
22
+ )
23
+
24
+
25
+ def _syntax_node_type_name(typ: SyntaxNodeType) -> str:
26
+ """Return the canonical name for a SyntaxNodeType (e.g. 'ntUnit')."""
27
+ try:
28
+ return SyntaxNodeType(typ).name
29
+ except ValueError:
30
+ return f"ntUnknown({typ})"
31
+
32
+
33
+ def _attribute_name_str(idx: int) -> str:
34
+ """Map an AttributeName int to its string name."""
35
+ if 0 <= idx < len(ATTRIBUTE_NAME_STRINGS):
36
+ return ATTRIBUTE_NAME_STRINGS[idx]
37
+ return f"attr({idx})"
38
+
39
+
40
+ def _source_positions_str(positions: tuple[tuple[int, int], ...]) -> str:
41
+ """Format a source_positions tuple as 'line:col,line:col,...'."""
42
+ parts = []
43
+ for line, col in positions:
44
+ parts.append(f"{line}:{col}")
45
+ return ",".join(parts)
46
+
47
+
48
+ def _serialize_node(
49
+ node: SyntaxNode,
50
+ lines: list[str],
51
+ indent: int,
52
+ *,
53
+ root_file_name: str,
54
+ ) -> None:
55
+ """Append one line per node (recursively) to *lines*."""
56
+ prefix = " " * indent
57
+
58
+ # Header: type, line, col, end_line, end_col
59
+ parts: list[str] = []
60
+ parts.append(_syntax_node_type_name(node.typ))
61
+ parts.append(f"line={node.line}")
62
+ parts.append(f"col={node.col}")
63
+ parts.append(f"end_line={node.token_end_line}")
64
+ parts.append(f"end_col={node.token_end_col}")
65
+
66
+ # Attributes alphabetically by name string
67
+ if node.has_attributes:
68
+ sorted_attrs = sorted(node.attributes, key=lambda pair: _attribute_name_str(pair[0].value))
69
+ for attr_key, attr_value in sorted_attrs:
70
+ parts.append(f"{_attribute_name_str(attr_key.value)}={_escape_value(attr_value)}")
71
+
72
+ # file_name — only when it differs from the root
73
+ if node.file_name and node.file_name != root_file_name:
74
+ parts.append(f"file_name={_escape_value(node.file_name)}")
75
+
76
+ # source_positions — only when non-empty
77
+ src_pos = node.source_positions
78
+ if src_pos:
79
+ parts.append(f"source_positions={_source_positions_str(src_pos)}")
80
+
81
+ lines.append(f"{prefix}{' '.join(parts)}")
82
+
83
+ # Subclass-specific extra fields
84
+ # CompoundSyntaxNode: end_line, end_col (already covered by token_end_line/col)
85
+ # ValuedSyntaxNode: value
86
+ node_value = getattr(node, "value", None)
87
+ if isinstance(node_value, str):
88
+ lines.append(f"{prefix} value={_escape_value(node_value)}")
89
+
90
+ # CommentNode: text
91
+ node_text = getattr(node, "text", None)
92
+ if isinstance(node_text, str):
93
+ lines.append(f"{prefix} text={_escape_value(node_text)}")
94
+
95
+ # Children in natural (insertion) order
96
+ for child in node.child_nodes:
97
+ _serialize_node(
98
+ child,
99
+ lines,
100
+ indent + 1,
101
+ root_file_name=root_file_name,
102
+ )
103
+
104
+
105
+ def serialize_syntax_tree(node: SyntaxNode) -> str:
106
+ """Return a deterministic text representation of the whole syntax tree.
107
+
108
+ Rules
109
+ -----
110
+ * One line per node, indented by depth (two spaces per level).
111
+ * Each node line: *type* *line=N col=N end_line=N end_col=N* [*attr=val* …]
112
+ * Attributes are sorted alphabetically by name.
113
+ * Children appear in natural (source) order.
114
+ * Values containing newlines / tabs are escaped so one node → one line.
115
+ * ``source_positions`` is output when non-empty (compact
116
+ ``line:col,line:col`` form, no truncation).
117
+ * ``file_name`` is output only when it differs from the root node's
118
+ ``file_name``. The root ``file_name`` itself is emitted once as
119
+ a header line at the top.
120
+ """
121
+ root_file_name = node.file_name
122
+ lines: list[str] = []
123
+ if root_file_name:
124
+ lines.append(f"# file_name={_escape_value(root_file_name)}")
125
+ _serialize_node(node, lines, 0, root_file_name=root_file_name)
126
+ return "\n".join(lines)
delphi_lsp/binary.py ADDED
@@ -0,0 +1,250 @@
1
+ from __future__ import annotations
2
+
3
+ import io
4
+ import struct
5
+ from enum import IntEnum
6
+ from typing import BinaryIO
7
+
8
+ from .consts import AttributeName, SyntaxNodeType
9
+ from .nodes import CommentNode, CompoundSyntaxNode, SyntaxNode, ValuedSyntaxNode
10
+
11
+
12
+ class BinarySerializerError(Exception):
13
+ pass
14
+
15
+
16
+ class NodeClass(IntEnum):
17
+ SYNTAX = 0
18
+ COMPOUND = 1
19
+ VALUED = 2
20
+ COMMENT = 3
21
+
22
+
23
+ _SIGNATURE = b'DAST binary file\x1a'
24
+ _VERSION = 0x01000000
25
+
26
+
27
+ class BinarySerializer:
28
+ def __init__(self) -> None:
29
+ self._string_list: list[str] = []
30
+ self._string_table: dict[str, int] = {}
31
+
32
+ def read(self, stream: BinaryIO) -> SyntaxNode:
33
+ self._string_list = []
34
+ if not self._check_signature(stream):
35
+ raise BinarySerializerError('invalid signature')
36
+ if not self._check_version(stream):
37
+ raise BinarySerializerError('unsupported version')
38
+ node = self._read_node(stream)
39
+ if node is None:
40
+ raise BinarySerializerError('failed to read node')
41
+ return node
42
+
43
+ def write(self, stream: BinaryIO, root: SyntaxNode) -> None:
44
+ self._string_table = {}
45
+ stream.write(_SIGNATURE)
46
+ stream.write(struct.pack('<I', _VERSION))
47
+ if not self._write_node(stream, root):
48
+ raise BinarySerializerError('failed to write node')
49
+
50
+ def _check_signature(self, stream: BinaryIO) -> bool:
51
+ data = stream.read(len(_SIGNATURE))
52
+ return data == _SIGNATURE
53
+
54
+ def _check_version(self, stream: BinaryIO) -> bool:
55
+ data = stream.read(4)
56
+ if len(data) != 4:
57
+ return False
58
+ version = struct.unpack('<I', data)[0]
59
+ return (version & 0xFFFF0000) == _VERSION
60
+
61
+ def _create_node(self, node_class: NodeClass, node_type: SyntaxNodeType) -> SyntaxNode:
62
+ if node_class == NodeClass.SYNTAX:
63
+ return SyntaxNode(node_type)
64
+ if node_class == NodeClass.COMPOUND:
65
+ return CompoundSyntaxNode(node_type)
66
+ if node_class == NodeClass.VALUED:
67
+ return ValuedSyntaxNode(node_type)
68
+ if node_class == NodeClass.COMMENT:
69
+ return CommentNode(node_type)
70
+ raise BinarySerializerError('unexpected node class')
71
+
72
+ def _read_node(self, stream: BinaryIO) -> SyntaxNode | None:
73
+ node_class_num = self._read_number(stream)
74
+ node_type_num = self._read_number(stream)
75
+ if node_class_num is None or node_type_num is None:
76
+ return None
77
+ try:
78
+ node_class = NodeClass(node_class_num)
79
+ node_type = SyntaxNodeType(node_type_num)
80
+ except ValueError:
81
+ raise BinarySerializerError('invalid node class or type')
82
+ node = self._create_node(node_class, node_type)
83
+
84
+ col = self._read_number(stream)
85
+ line = self._read_number(stream)
86
+ if col is None or line is None:
87
+ return None
88
+ node.col = col
89
+ node.line = line
90
+
91
+ if node_class == NodeClass.COMPOUND:
92
+ end_col = self._read_number(stream)
93
+ end_line = self._read_number(stream)
94
+ if end_col is None or end_line is None:
95
+ return None
96
+ node.end_col = end_col
97
+ node.end_line = end_line
98
+ elif node_class == NodeClass.VALUED:
99
+ value = self._read_string(stream)
100
+ if value is None:
101
+ return None
102
+ node.value = value
103
+ elif node_class == NodeClass.COMMENT:
104
+ text = self._read_string(stream)
105
+ if text is None:
106
+ return None
107
+ node.text = text
108
+
109
+ attr_count = self._read_number(stream)
110
+ if attr_count is None:
111
+ return None
112
+ for _ in range(attr_count):
113
+ attr_key_num = self._read_number(stream)
114
+ if attr_key_num is None:
115
+ return None
116
+ try:
117
+ attr_key = AttributeName(attr_key_num)
118
+ except ValueError:
119
+ raise BinarySerializerError('invalid attribute key')
120
+ attr_value = self._read_string(stream)
121
+ if attr_value is None:
122
+ return None
123
+ node.set_attribute(attr_key, attr_value)
124
+
125
+ child_count = self._read_number(stream)
126
+ if child_count is None:
127
+ return None
128
+ for _ in range(child_count):
129
+ child_node = self._read_node(stream)
130
+ if child_node is None:
131
+ return None
132
+ node.add_child(child_node)
133
+
134
+ return node
135
+
136
+ def _read_number(self, stream: BinaryIO) -> int | None:
137
+ shift = 0
138
+ num = 0
139
+ while True:
140
+ data = stream.read(1)
141
+ if len(data) != 1:
142
+ return None
143
+ low = data[0]
144
+ num |= (low & 0x7F) << shift
145
+ shift += 7
146
+ if (low & 0x80) == 0:
147
+ return num
148
+
149
+ def _read_string(self, stream: BinaryIO) -> str | None:
150
+ length = self._read_number(stream)
151
+ if length is None:
152
+ return None
153
+ if (length >> 24) == 0xFF:
154
+ string_id = length & 0x00FFFFFF
155
+ if string_id >= len(self._string_list):
156
+ return None
157
+ return self._string_list[string_id]
158
+ data = stream.read(length) if length else b''
159
+ if len(data) != length:
160
+ return None
161
+ value = data.decode('utf-8')
162
+ if len(value) > 4:
163
+ self._string_list.append(value)
164
+ return value
165
+
166
+ def _write_node(self, stream: BinaryIO, node: SyntaxNode) -> bool:
167
+ if isinstance(node, CompoundSyntaxNode):
168
+ node_class = NodeClass.COMPOUND
169
+ elif isinstance(node, ValuedSyntaxNode):
170
+ node_class = NodeClass.VALUED
171
+ elif isinstance(node, CommentNode):
172
+ node_class = NodeClass.COMMENT
173
+ else:
174
+ node_class = NodeClass.SYNTAX
175
+
176
+ if not self._write_number(stream, int(node_class)):
177
+ return False
178
+ if not self._write_number(stream, int(node.typ)):
179
+ return False
180
+ if not self._write_number(stream, node.col):
181
+ return False
182
+ if not self._write_number(stream, node.line):
183
+ return False
184
+
185
+ if node_class == NodeClass.COMPOUND:
186
+ if not self._write_number(stream, node.end_col):
187
+ return False
188
+ if not self._write_number(stream, node.end_line):
189
+ return False
190
+ elif node_class == NodeClass.VALUED:
191
+ if not self._write_string(stream, node.value):
192
+ return False
193
+ elif node_class == NodeClass.COMMENT:
194
+ if not self._write_string(stream, node.text):
195
+ return False
196
+
197
+ if not self._write_number(stream, len(node.attributes)):
198
+ return False
199
+ for attr_key, attr_value in node.attributes:
200
+ if not self._write_number(stream, int(attr_key)):
201
+ return False
202
+ if not self._write_string(stream, attr_value):
203
+ return False
204
+
205
+ if not self._write_number(stream, len(node.child_nodes)):
206
+ return False
207
+ for child_node in node.child_nodes:
208
+ if not self._write_node(stream, child_node):
209
+ return False
210
+
211
+ return True
212
+
213
+ def _write_number(self, stream: BinaryIO, num: int) -> bool:
214
+ value = num
215
+ while True:
216
+ low = value & 0x7F
217
+ value >>= 7
218
+ if value != 0:
219
+ low |= 0x80
220
+ if stream.write(bytes([low])) != 1:
221
+ return False
222
+ if value == 0:
223
+ return True
224
+
225
+ def _write_string(self, stream: BinaryIO, value: str) -> bool:
226
+ if len(value) > 4 and value in self._string_table:
227
+ string_id = self._string_table[value]
228
+ return self._write_number(stream, string_id | 0xFF000000)
229
+
230
+ if len(value) > 4:
231
+ self._string_table[value] = len(self._string_table)
232
+ if self._string_table[value] > 0xFFFFFF:
233
+ raise BinarySerializerError('too many strings')
234
+
235
+ data = value.encode('utf-8')
236
+ if not self._write_number(stream, len(data)):
237
+ return False
238
+ if data:
239
+ return stream.write(data) == len(data)
240
+ return True
241
+
242
+
243
+ def loads(data: bytes) -> SyntaxNode:
244
+ return BinarySerializer().read(io.BytesIO(data))
245
+
246
+
247
+ def dumps(node: SyntaxNode) -> bytes:
248
+ stream = io.BytesIO()
249
+ BinarySerializer().write(stream, node)
250
+ return stream.getvalue()
@@ -0,0 +1,28 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Iterable
4
+
5
+ from .consts import SyntaxNodeType
6
+ from .nodes import CommentNode
7
+ from .preprocessor import CommentInfo
8
+
9
+ _KIND_TO_NODE_TYPE = {
10
+ 'ansi': SyntaxNodeType.ntAnsiComment,
11
+ 'borland': SyntaxNodeType.ntBorComment,
12
+ 'slashes': SyntaxNodeType.ntSlashesComment,
13
+ }
14
+
15
+
16
+ def build_comment_nodes(comments: Iterable[CommentInfo]) -> list[CommentNode]:
17
+ nodes: list[CommentNode] = []
18
+ for info in comments:
19
+ node_type = _KIND_TO_NODE_TYPE.get(info.kind)
20
+ if node_type is None:
21
+ raise ValueError(f'unknown comment kind: {info.kind}')
22
+ node = CommentNode(node_type)
23
+ node.text = info.text
24
+ node.file_name = info.file_name
25
+ node.line = info.line
26
+ node.col = info.col
27
+ nodes.append(node)
28
+ return nodes