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,399 @@
1
+ """Linear Delphi/Object Pascal lexer ported from DelphiAST/SimpleParser.
2
+
3
+ The implementation intentionally uses direct character dispatch rather than a
4
+ backtracking regular-expression lexer. Source: DelphiAST, MPL-2.0,
5
+ https://github.com/RomanYankovsky/DelphiAST
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import re
11
+
12
+ from .delphiast_tokens import Token, TokenKind
13
+ from .lark_tokens import KEYWORDS
14
+ from .lexical_scanner import scan_brace_comment
15
+
16
+ _KEYWORDS = frozenset(value.casefold() for value in KEYWORDS)
17
+ _COMPOUND_SYMBOLS = frozenset(
18
+ {
19
+ ":=",
20
+ "<=",
21
+ ">=",
22
+ "<>",
23
+ "..",
24
+ "**",
25
+ "+=",
26
+ "-=",
27
+ "*=",
28
+ "/=",
29
+ "><",
30
+ "=>",
31
+ }
32
+ )
33
+ _HEX_DIGITS = frozenset("0123456789abcdefABCDEF")
34
+ _WHITESPACE_RE = re.compile(r"\s+")
35
+ _IDENTIFIER_PART_RE = re.compile(r"\w*")
36
+ _INITIALIZER_SPECIAL_RE = re.compile(r"['/{}\[\]()]")
37
+ _LARGE_INITIALIZER_CANDIDATE_RE = re.compile(r"(?<!:)=\s*\(")
38
+ _SIMPLE_TOKEN_RE = re.compile(
39
+ r"\s+"
40
+ r"|(?:[^\W\d]|_)\w*"
41
+ r"|\d+(?:\.(?!\.)\d*)?(?:[eE][+-]?\d+)?"
42
+ r"|:=|<=|>=|<>|\.\.|\*\*|\+=|-=|\*=|/=|><|=>"
43
+ r"|[;,:.=+\-*/()\[\]^@<>]"
44
+ r"|.",
45
+ re.DOTALL,
46
+ )
47
+ _SIMPLE_SOURCE_MARKERS = ("'", "{", "//", "(*", "#", "$", "%", "&")
48
+
49
+
50
+ class DelphiAstLexer:
51
+ def __init__(self, source: str, file_name: str = "") -> None:
52
+ self.source = source
53
+ self.file_name = file_name
54
+ self._length = len(source)
55
+ self._index = 0
56
+ self._line = 1
57
+ self._column = 1
58
+ self._previous_significant = ""
59
+
60
+ def tokenize(self, *, include_trivia: bool = True) -> list[Token]:
61
+ if self._is_simple_source():
62
+ return self._tokenize_simple()
63
+ tokens: list[Token] = []
64
+ while self._index < self._length:
65
+ character = self.source[self._index]
66
+ if character.isspace():
67
+ whitespace = _WHITESPACE_RE.match(self.source, self._index)
68
+ assert whitespace is not None
69
+ self._advance_to(whitespace.end())
70
+ continue
71
+ if not include_trivia:
72
+ trivia_end = self._trivia_end()
73
+ if trivia_end is not None:
74
+ self._advance_to(trivia_end)
75
+ continue
76
+ token = self._next_token()
77
+ if include_trivia or token.kind not in {
78
+ TokenKind.COMMENT,
79
+ TokenKind.DIRECTIVE,
80
+ }:
81
+ tokens.append(token)
82
+ if token.kind not in {TokenKind.COMMENT, TokenKind.DIRECTIVE}:
83
+ self._previous_significant = token.normalized
84
+ if token.end <= token.start: # Defensive forward-progress guard.
85
+ self._advance_to(self._index + 1)
86
+ tokens.append(
87
+ Token(
88
+ TokenKind.EOF,
89
+ "",
90
+ self._line,
91
+ self._column,
92
+ self._index,
93
+ self._index,
94
+ self.file_name,
95
+ )
96
+ )
97
+ return tokens
98
+
99
+ def _is_simple_source(self) -> bool:
100
+ return not any(marker in self.source for marker in _SIMPLE_SOURCE_MARKERS) and (
101
+ len(self.source) < 64 * 1024
102
+ or _LARGE_INITIALIZER_CANDIDATE_RE.search(self.source) is None
103
+ )
104
+
105
+ def _tokenize_simple(self) -> list[Token]:
106
+ tokens: list[Token] = []
107
+ line = 1
108
+ column = 1
109
+ for match in _SIMPLE_TOKEN_RE.finditer(self.source):
110
+ value = match.group()
111
+ if value.isspace():
112
+ newline_count = value.count("\n")
113
+ if newline_count:
114
+ line += newline_count
115
+ column = len(value.rsplit("\n", 1)[-1]) + 1
116
+ else:
117
+ column += len(value)
118
+ continue
119
+ first = value[0]
120
+ if _identifier_start(first):
121
+ kind = (
122
+ TokenKind.KEYWORD
123
+ if value.casefold() in _KEYWORDS
124
+ else TokenKind.IDENTIFIER
125
+ )
126
+ elif first.isdigit():
127
+ kind = TokenKind.NUMBER
128
+ elif value in _COMPOUND_SYMBOLS or first in ";,:.=+-*/()[]^@<>":
129
+ kind = TokenKind.SYMBOL
130
+ else:
131
+ kind = TokenKind.UNKNOWN
132
+ tokens.append(
133
+ Token(
134
+ kind,
135
+ value,
136
+ line,
137
+ column,
138
+ match.start(),
139
+ match.end(),
140
+ self.file_name,
141
+ )
142
+ )
143
+ column += len(value)
144
+ self._index = self._length
145
+ self._line = line
146
+ self._column = column
147
+ tokens.append(
148
+ Token(
149
+ TokenKind.EOF,
150
+ "",
151
+ line,
152
+ column,
153
+ self._length,
154
+ self._length,
155
+ self.file_name,
156
+ )
157
+ )
158
+ return tokens
159
+
160
+ def _trivia_end(self) -> int | None:
161
+ start = self._index
162
+ character = self.source[start]
163
+ following = self.source[start + 1] if start + 1 < self._length else ""
164
+ if character == "/" and following == "/":
165
+ end = self.source.find("\n", start + 2)
166
+ return self._length if end < 0 else end
167
+ if character == "{":
168
+ end, _ = scan_brace_comment(self.source, start)
169
+ return end
170
+ if character == "(" and following == "*":
171
+ close = self.source.find("*)", start + 2)
172
+ return self._length if close < 0 else close + 2
173
+ return None
174
+
175
+ def _next_token(self) -> Token:
176
+ start = self._index
177
+ line = self._line
178
+ column = self._column
179
+ character = self.source[start]
180
+ following = self.source[start + 1] if start + 1 < self._length else ""
181
+
182
+ if character == "/" and following == "/":
183
+ end = self.source.find("\n", start + 2)
184
+ end = self._length if end < 0 else end
185
+ return self._emit(TokenKind.COMMENT, start, end, line, column)
186
+ if character == "{":
187
+ end, _ = scan_brace_comment(self.source, start)
188
+ kind = TokenKind.DIRECTIVE if following == "$" else TokenKind.COMMENT
189
+ return self._emit(kind, start, end, line, column)
190
+ if character == "(" and following == "*":
191
+ end = self.source.find("*)", start + 2)
192
+ end = self._length if end < 0 else end + 2
193
+ directive = start + 2 < self._length and self.source[start + 2] == "$"
194
+ return self._emit(
195
+ TokenKind.DIRECTIVE if directive else TokenKind.COMMENT,
196
+ start,
197
+ end,
198
+ line,
199
+ column,
200
+ )
201
+ if character == "'":
202
+ end = self._quoted_end(start)
203
+ return self._emit(TokenKind.STRING, start, end, line, column)
204
+ if character == "(" and self._previous_significant == "=":
205
+ initializer_end = self._large_initializer_end(start)
206
+ if initializer_end is not None:
207
+ return self._emit(
208
+ TokenKind.UNKNOWN,
209
+ start,
210
+ initializer_end,
211
+ line,
212
+ column,
213
+ value="<large-initializer>",
214
+ )
215
+ if character == "#":
216
+ end = start + 1
217
+ if end < self._length and self.source[end] == "$":
218
+ end += 1
219
+ while end < self._length and self.source[end] in _HEX_DIGITS:
220
+ end += 1
221
+ else:
222
+ while end < self._length and self.source[end].isdigit():
223
+ end += 1
224
+ return self._emit(TokenKind.CHAR_CODE, start, max(end, start + 1), line, column)
225
+ if character == "$":
226
+ end = start + 1
227
+ while end < self._length and self.source[end] in _HEX_DIGITS:
228
+ end += 1
229
+ return self._emit(TokenKind.NUMBER, start, end, line, column)
230
+ if character == "%" and following in {"0", "1"}:
231
+ end = start + 1
232
+ while end < self._length and self.source[end] in {"0", "1"}:
233
+ end += 1
234
+ return self._emit(TokenKind.NUMBER, start, end, line, column)
235
+ if character == "&" and following.isdigit():
236
+ end = start + 1
237
+ while end < self._length and self.source[end] in "01234567":
238
+ end += 1
239
+ return self._emit(TokenKind.NUMBER, start, end, line, column)
240
+ if character == "&" and _identifier_start(following):
241
+ end = start + 2
242
+ identifier_tail = _IDENTIFIER_PART_RE.match(self.source, end)
243
+ assert identifier_tail is not None
244
+ end = identifier_tail.end()
245
+ token = self._emit(TokenKind.IDENTIFIER, start, end, line, column)
246
+ return Token(
247
+ token.kind,
248
+ token.value[1:],
249
+ token.line,
250
+ token.column,
251
+ token.start,
252
+ token.end,
253
+ token.file_name,
254
+ )
255
+ if _identifier_start(character):
256
+ end = start + 1
257
+ identifier_tail = _IDENTIFIER_PART_RE.match(self.source, end)
258
+ assert identifier_tail is not None
259
+ end = identifier_tail.end()
260
+ value = self.source[start:end]
261
+ kind = TokenKind.KEYWORD if value.casefold() in _KEYWORDS else TokenKind.IDENTIFIER
262
+ return self._emit(kind, start, end, line, column)
263
+ if character.isdigit():
264
+ end = self._number_end(start)
265
+ return self._emit(TokenKind.NUMBER, start, end, line, column)
266
+
267
+ compound = self.source[start : start + 2]
268
+ if compound in _COMPOUND_SYMBOLS:
269
+ return self._emit(TokenKind.SYMBOL, start, start + 2, line, column)
270
+ if character in ";,:.=+-*/()[]^@<>":
271
+ return self._emit(TokenKind.SYMBOL, start, start + 1, line, column)
272
+ return self._emit(TokenKind.UNKNOWN, start, start + 1, line, column)
273
+
274
+ def _number_end(self, start: int) -> int:
275
+ end = start
276
+ while end < self._length and self.source[end].isdigit():
277
+ end += 1
278
+ if (
279
+ end < self._length
280
+ and self.source[end] == "."
281
+ and self.source[end : end + 2] != ".."
282
+ ):
283
+ end += 1
284
+ while end < self._length and self.source[end].isdigit():
285
+ end += 1
286
+ if end < self._length and self.source[end] in {"e", "E"}:
287
+ exponent = end + 1
288
+ if exponent < self._length and self.source[exponent] in {"+", "-"}:
289
+ exponent += 1
290
+ digit_start = exponent
291
+ while exponent < self._length and self.source[exponent].isdigit():
292
+ exponent += 1
293
+ if exponent > digit_start:
294
+ end = exponent
295
+ return end
296
+
297
+ def _quoted_end(self, start: int) -> int:
298
+ for delimiter in ("'''''", "'''"):
299
+ opening_end = start + len(delimiter)
300
+ if not self.source.startswith(delimiter, start):
301
+ continue
302
+ if self.source[opening_end : opening_end + 1] not in {"\r", "\n"}:
303
+ continue
304
+ close = self.source.find(delimiter, opening_end)
305
+ return self._length if close < 0 else close + len(delimiter)
306
+ end = start + 1
307
+ while end < self._length:
308
+ if self.source[end] in {"\r", "\n"}:
309
+ return end
310
+ if self.source[end] != "'":
311
+ end += 1
312
+ continue
313
+ if end + 1 < self._length and self.source[end + 1] == "'":
314
+ end += 2
315
+ continue
316
+ return end + 1
317
+ return self._length
318
+
319
+ def _large_initializer_end(self, start: int) -> int | None:
320
+ first_special = _INITIALIZER_SPECIAL_RE.search(self.source, start + 1)
321
+ if first_special is not None and first_special.group() == ")":
322
+ end = first_special.end()
323
+ return end if end - start >= 64 * 1024 else None
324
+ stack = [")"]
325
+ index = start + 1
326
+ while index < self._length:
327
+ special = _INITIALIZER_SPECIAL_RE.search(self.source, index)
328
+ if special is None:
329
+ return None
330
+ index = special.start()
331
+ character = special.group()
332
+ following = self.source[index + 1] if index + 1 < self._length else ""
333
+ if character == "'":
334
+ index = self._quoted_end(index)
335
+ continue
336
+ if character == "/" and following == "/":
337
+ newline = self.source.find("\n", index + 2)
338
+ index = self._length if newline < 0 else newline + 1
339
+ continue
340
+ if character == "{":
341
+ index, _ = scan_brace_comment(self.source, index)
342
+ continue
343
+ if character == "(" and following == "*":
344
+ close = self.source.find("*)", index + 2)
345
+ index = self._length if close < 0 else close + 2
346
+ continue
347
+ if character == "(":
348
+ stack.append(")")
349
+ elif character == "[":
350
+ stack.append("]")
351
+ elif stack and character == stack[-1]:
352
+ stack.pop()
353
+ if not stack:
354
+ end = index + 1
355
+ return end if end - start >= 64 * 1024 else None
356
+ index = special.end()
357
+ return None
358
+
359
+ def _emit(
360
+ self,
361
+ kind: TokenKind,
362
+ start: int,
363
+ end: int,
364
+ line: int,
365
+ column: int,
366
+ *,
367
+ value: str | None = None,
368
+ ) -> Token:
369
+ token_value = self.source[start:end] if value is None else value
370
+ self._advance_to(end)
371
+ return Token(kind, token_value, line, column, start, end, self.file_name)
372
+
373
+ def _advance_to(self, end: int) -> None:
374
+ if end <= self._index:
375
+ return
376
+ newline_count = self.source.count("\n", self._index, end)
377
+ if newline_count:
378
+ self._line += newline_count
379
+ last_newline = self.source.rfind("\n", self._index, end)
380
+ self._column = end - last_newline
381
+ else:
382
+ self._column += end - self._index
383
+ self._index = end
384
+
385
+
386
+ def large_initializer_end(source: str, start: int) -> int | None:
387
+ """Return the closing offset for an initializer large enough to compact."""
388
+ return DelphiAstLexer(source)._large_initializer_end(start)
389
+
390
+
391
+ def _identifier_start(character: str) -> bool:
392
+ return bool(character) and (character == "_" or character.isalpha())
393
+
394
+
395
+ def _identifier_part(character: str) -> bool:
396
+ return character == "_" or character.isalnum()
397
+
398
+
399
+ __all__ = ["DelphiAstLexer", "large_initializer_end"]