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
|
@@ -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
|