python-delphi-lsp 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.
@@ -0,0 +1,15 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+
6
+ def read_source_text(path: Path) -> str:
7
+ data = path.read_bytes()
8
+ if data.startswith(b'\xff\xfe') or data.startswith(b'\xfe\xff'):
9
+ return data.decode('utf-16')
10
+ if data.startswith(b'\xef\xbb\xbf'):
11
+ return data.decode('utf-8-sig')
12
+ try:
13
+ return data.decode('utf-8')
14
+ except UnicodeDecodeError:
15
+ return data.decode('latin-1')
delphiast/workspace.py ADDED
@@ -0,0 +1,53 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Iterable
5
+
6
+ from .consts import AttributeName
7
+ from .parser import DelphiParser
8
+ from .semantic import Scope, ScopeKind, SymbolIndex
9
+ from .semantic_builder import SemanticBuilder, SemanticModel
10
+
11
+
12
+ @dataclass
13
+ class WorkspaceSemanticResult:
14
+ models: dict[str, SemanticModel]
15
+ index: SymbolIndex
16
+
17
+
18
+ def build_workspace_semantics(
19
+ sources: dict[str, str],
20
+ *,
21
+ include_paths: Iterable[str] = (),
22
+ defines: Iterable[str] = (),
23
+ preprocessor_options=None,
24
+ collect_references: bool = True,
25
+ ) -> WorkspaceSemanticResult:
26
+ parser = DelphiParser(
27
+ include_paths=include_paths,
28
+ defines=defines,
29
+ preprocessor_options=preprocessor_options,
30
+ )
31
+ roots: dict[str, object] = {}
32
+ for file_name, text in sources.items():
33
+ result = parser.parse(text, file_name, build_semantic=False)
34
+ roots[file_name] = result.root
35
+
36
+ index = SymbolIndex()
37
+ scopes: dict[str, Scope] = {}
38
+ for file_name, root in roots.items():
39
+ unit_name = root.get_attribute(AttributeName.anName) or file_name
40
+ unit_scope = Scope(kind=ScopeKind.UNIT, name=unit_name)
41
+ index.register_unit(unit_name, unit_scope, index_symbols=False)
42
+ scopes[file_name] = unit_scope
43
+
44
+ builder = SemanticBuilder(collect_references=collect_references)
45
+ for file_name, root in roots.items():
46
+ builder.declare(root, index=index, unit_scope=scopes[file_name], reset_state=False)
47
+
48
+ models: dict[str, SemanticModel] = {}
49
+ for file_name, root in roots.items():
50
+ model = builder.resolve(root, scopes[file_name])
51
+ models[file_name] = model
52
+
53
+ return WorkspaceSemanticResult(models=models, index=index)
delphiast/writer.py ADDED
@@ -0,0 +1,73 @@
1
+ from __future__ import annotations
2
+
3
+ from .consts import ATTRIBUTE_NAME_STRINGS, SYNTAX_NODE_NAMES
4
+ from .nodes import CompoundSyntaxNode, SyntaxNode, ValuedSyntaxNode
5
+
6
+
7
+ def _xml_encode(data: str) -> str:
8
+ result = []
9
+ for ch in data:
10
+ if ch == '<':
11
+ result.append('&lt;')
12
+ elif ch == '>':
13
+ result.append('&gt;')
14
+ elif ch == '&':
15
+ result.append('&amp;')
16
+ elif ch == '"':
17
+ result.append('&quot;')
18
+ elif ch == "'":
19
+ result.append('&apos;')
20
+ else:
21
+ result.append(ch)
22
+ return ''.join(result)
23
+
24
+
25
+ class SyntaxTreeWriter:
26
+ @staticmethod
27
+ def to_xml(root: SyntaxNode, formatted: bool = False) -> str:
28
+ builder: list[str] = []
29
+ SyntaxTreeWriter._node_to_xml(builder, root, formatted, '')
30
+ return '<?xml version="1.0"?>\n' + ''.join(builder)
31
+
32
+ @staticmethod
33
+ def _node_to_xml(builder: list[str], node: SyntaxNode, formatted: bool, indent: str) -> None:
34
+ has_children = node.has_children
35
+ new_indent = indent + ' '
36
+ if formatted:
37
+ builder.append(indent)
38
+ builder.append('<' + SYNTAX_NODE_NAMES[node.typ].upper())
39
+
40
+ if isinstance(node, CompoundSyntaxNode):
41
+ builder.append(f' begin_line="{node.line}"')
42
+ builder.append(f' begin_col="{node.col}"')
43
+ builder.append(f' end_line="{node.end_line}"')
44
+ builder.append(f' end_col="{node.end_col}"')
45
+ else:
46
+ builder.append(f' line="{node.line}"')
47
+ builder.append(f' col="{node.col}"')
48
+
49
+ if node.file_name:
50
+ builder.append(' file="' + _xml_encode(node.file_name) + '"')
51
+
52
+ if isinstance(node, ValuedSyntaxNode):
53
+ builder.append(' value="' + _xml_encode(node.value) + '"')
54
+
55
+ for attr_key, attr_value in node.attributes:
56
+ builder.append(
57
+ ' ' + ATTRIBUTE_NAME_STRINGS[attr_key] + '="' + _xml_encode(attr_value) + '"'
58
+ )
59
+
60
+ if has_children:
61
+ builder.append('>')
62
+ else:
63
+ builder.append('/>')
64
+ if formatted:
65
+ builder.append('\n')
66
+ for child in node.child_nodes:
67
+ SyntaxTreeWriter._node_to_xml(builder, child, formatted, new_indent)
68
+ if has_children:
69
+ if formatted:
70
+ builder.append(indent)
71
+ builder.append('</' + SYNTAX_NODE_NAMES[node.typ].upper() + '>')
72
+ if formatted:
73
+ builder.append('\n')
@@ -0,0 +1,280 @@
1
+ Metadata-Version: 2.4
2
+ Name: python-delphi-lsp
3
+ Version: 1.0.0
4
+ Summary: Python Delphi/Object Pascal parser, semantic indexer, and language server.
5
+ Author: Dark Light
6
+ License-Expression: MPL-2.0
7
+ Keywords: delphi,object-pascal,pascal,lsp,language-server,parser
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Operating System :: MacOS
12
+ Classifier: Operating System :: Microsoft :: Windows
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Topic :: Software Development :: Compilers
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Requires-Python: >=3.10
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: lark>=1.1.9
25
+ Requires-Dist: pygls<2.0,>=1.3.0
26
+ Requires-Dist: lsprotocol>=2023.0.1
27
+ Provides-Extra: lsp
28
+ Requires-Dist: pygls<2.0,>=1.3.0; extra == "lsp"
29
+ Requires-Dist: lsprotocol>=2023.0.1; extra == "lsp"
30
+ Provides-Extra: test
31
+ Requires-Dist: pytest>=8.0.0; extra == "test"
32
+ Provides-Extra: dev
33
+ Requires-Dist: build>=1.2.0; extra == "dev"
34
+ Requires-Dist: twine>=5.0.0; extra == "dev"
35
+ Requires-Dist: pygls<2.0,>=1.3.0; extra == "dev"
36
+ Requires-Dist: lsprotocol>=2023.0.1; extra == "dev"
37
+ Requires-Dist: pytest>=8.0.0; extra == "dev"
38
+ Dynamic: license-file
39
+
40
+ # Python Delphi LSP
41
+
42
+ Python Delphi LSP is a standalone Python package for Delphi/Object Pascal
43
+ parsing, semantic indexing, diagnostics, and Language Server Protocol support.
44
+
45
+ The distributable package is named `python-delphi-lsp`. The import package keeps
46
+ the established `delphiast` name, and the language-server executable is
47
+ `delphi-lsp`.
48
+
49
+ ## What It Provides
50
+
51
+ - Parser support for `.pas`, `.dpr`, `.dpk`, and `.inc` files
52
+ - Delphi preprocessor handling for include files, conditionals, and compiler
53
+ directives
54
+ - Semantic symbols for units, types, methods, fields, properties, variables,
55
+ constants, and references
56
+ - Workspace indexing across Delphi projects
57
+ - LSP support for document symbols, workspace symbols, hover, definition,
58
+ references, rename, completion, and diagnostics
59
+ - opencode integration through the experimental LSP tool
60
+
61
+ ## Installation
62
+
63
+ Install the package from a built distribution or from PyPI once published:
64
+
65
+ ```bash
66
+ python -m pip install python-delphi-lsp
67
+ ```
68
+
69
+ For development from a checkout:
70
+
71
+ ```bash
72
+ python -m venv .venv
73
+ . .venv/bin/activate
74
+ python -m pip install -e ".[dev]"
75
+ python -m pytest -q
76
+ ```
77
+
78
+ On Windows PowerShell:
79
+
80
+ ```powershell
81
+ python -m venv .venv
82
+ .\.venv\Scripts\Activate.ps1
83
+ python -m pip install -e ".[dev]"
84
+ python -m pytest -q
85
+ ```
86
+
87
+ ## Python API Example
88
+
89
+ ```python
90
+ from delphiast import parse
91
+
92
+ result = parse("unit Unit1; interface implementation end.", "Unit1.pas")
93
+ print(result.root)
94
+ ```
95
+
96
+ Enable semantic analysis when you need symbols or diagnostics:
97
+
98
+ ```python
99
+ from delphiast import parse
100
+
101
+ source = """
102
+ unit Unit1;
103
+
104
+ interface
105
+
106
+ type
107
+ TGreeter = class
108
+ public
109
+ procedure SayHello;
110
+ end;
111
+
112
+ implementation
113
+
114
+ procedure TGreeter.SayHello;
115
+ begin
116
+ end;
117
+
118
+ end.
119
+ """
120
+
121
+ result = parse(source, "Unit1.pas", build_semantic=True)
122
+ for symbol in result.semantic.symbols:
123
+ print(symbol.name, symbol.kind)
124
+ ```
125
+
126
+ ## Language Server Usage
127
+
128
+ Start the LSP server over stdio:
129
+
130
+ ```bash
131
+ delphi-lsp
132
+ ```
133
+
134
+ From a checkout, the equivalent command is:
135
+
136
+ ```bash
137
+ python -m delphiast.lsp_server
138
+ ```
139
+
140
+ The server expects normal LSP JSON-RPC over stdio. Editors and tools should set
141
+ the workspace root to the Delphi project directory and pass any include paths or
142
+ compiler defines through LSP initialization options when needed.
143
+
144
+ ## opencode Usage
145
+
146
+ This repository includes an `opencode.json` that registers the Delphi LSP tool
147
+ and model aliases for local Ollama and vLLM endpoints.
148
+
149
+ For normal local opencode work, use the Ollama alias with a larger context:
150
+
151
+ ```bash
152
+ opencode run --dir . --model ollama/ornith-lspctx
153
+ ```
154
+
155
+ For large Delphi files, prefer LSP operations over reading the file into the
156
+ model prompt. The reduced `vllm-lsp` agent disables filesystem and shell tools
157
+ and leaves only the LSP tool enabled:
158
+
159
+ ```bash
160
+ OPENCODE_EXPERIMENTAL_LSP_TOOL=true \
161
+ python scripts/run_opencode_lsp_probe.py \
162
+ --cwd output/mega_lsp_chain_project \
163
+ --model vllm/ornith-lspctx \
164
+ --agent vllm-lsp \
165
+ --require-tool lsp.workspaceSymbol:MegaProc02500 \
166
+ --forbid-tool bash --forbid-tool read --forbid-tool glob --forbid-tool grep \
167
+ --forbid-tool edit --forbid-tool write --forbid-tool task \
168
+ --forbid-tool webfetch --forbid-tool todowrite --forbid-tool skill \
169
+ 'Use only the Delphi LSP tool. In file Mega100kUnit.pas, run workspaceSymbol with filePath "Mega100kUnit.pas", line 1, character 1, and query "MegaProc02500".'
170
+ ```
171
+
172
+ For an LSP-first edit proof, use `vllm-lsp-edit`. It permits the opencode edit
173
+ tool after LSP lookup while still forbidding shell and direct file-reading tools:
174
+
175
+ ```bash
176
+ OPENCODE_EXPERIMENTAL_LSP_TOOL=true \
177
+ python scripts/run_opencode_lsp_probe.py \
178
+ --cwd output/mega_lsp_chain_project \
179
+ --model vllm/ornith-lspctx \
180
+ --agent vllm-lsp-edit \
181
+ --require-tool lsp.workspaceSymbol:MegaProc02500 \
182
+ --require-tool 'edit:Edit applied successfully' \
183
+ --forbid-tool bash --forbid-tool read --forbid-tool glob --forbid-tool grep \
184
+ --forbid-tool write --forbid-tool task --forbid-tool webfetch \
185
+ --forbid-tool todowrite --forbid-tool skill \
186
+ 'Use LSP first, then edit the exact MegaProc02500 block.'
187
+ ```
188
+
189
+ ## Reproducing the vLLM opencode Test
190
+
191
+ The vLLM test is designed to prove that opencode can work on Delphi files larger
192
+ than the model context by calling LSP instead of loading the source file into the
193
+ prompt.
194
+
195
+ The test does the following:
196
+
197
+ 1. Creates `output/mega_lsp_chain_project/Mega100kUnit.pas`, a generated Delphi
198
+ unit with more than 100,000 lines and the symbol `MegaProc02500`.
199
+ 2. Writes an opencode sandbox config with an absolute `delphi-lsp` command and
200
+ `PYTHONPATH` pointing at the checkout.
201
+ 3. Uses `vllm/ornith-lspctx` with the reduced `vllm-lsp` agent.
202
+ 4. Requires a completed `lsp.workspaceSymbol` tool call that returns
203
+ `MegaProc02500`.
204
+ 5. Fails immediately if opencode calls `read`, `bash`, `glob`, `grep`, `edit`,
205
+ `write`, `task`, `webfetch`, `todowrite`, or `skill`.
206
+
207
+ On macOS, start the local vLLM helper and run the proof:
208
+
209
+ ```bash
210
+ scripts/bootstrap_vllm_opencode_test.sh --start-vllm
211
+ ```
212
+
213
+ By default the vLLM helper is offline-only. It checks the Hugging Face cache and
214
+ does not download model shards. Pass `--allow-download` only when you explicitly
215
+ want the helper to fill missing cache files.
216
+
217
+ On Windows PowerShell, use an already running vLLM-compatible endpoint:
218
+
219
+ ```powershell
220
+ .\scripts\bootstrap_vllm_opencode_test.ps1 -UseRunningServer
221
+ ```
222
+
223
+ Use a custom endpoint when vLLM runs in WSL, Docker, or on another machine:
224
+
225
+ ```powershell
226
+ .\scripts\bootstrap_vllm_opencode_test.ps1 -UseRunningServer -BaseUrl "http://127.0.0.1:8001/v1"
227
+ ```
228
+
229
+ The macOS helper uses these defaults:
230
+
231
+ - `MODEL_ID=deepreinforce-ai/Ornith-1.0-9B`
232
+ - `SERVED_MODEL_NAME=ornith-vllm-metal`
233
+ - `MAX_MODEL_LEN=44352`
234
+ - `MAX_NUM_SEQS=1`
235
+ - `VLLM_METAL_MEMORY_FRACTION=0.97`
236
+ - `TOOL_CALL_PARSER=qwen3_xml`
237
+
238
+ The release evidence from the local proof recorded:
239
+
240
+ - default opencode request: 29,318 system-prompt characters and 10 tool schemas
241
+ - reduced LSP-only request: 8,978 system-prompt characters and 1 tool schema
242
+ - generated test unit: 117k lines
243
+ - GitHub corpus file: 14,309 lines
244
+ - `context_budget.status = "pass"`
245
+ - `goal_audit.status = "pass"`
246
+
247
+ ## Verification
248
+
249
+ Run the local test suite:
250
+
251
+ ```bash
252
+ python -m pytest -q
253
+ ```
254
+
255
+ Generate the Delphi language-feature matrix:
256
+
257
+ ```bash
258
+ python scripts/audit_delphi_language_features.py
259
+ ```
260
+
261
+ Build and check distributable artifacts:
262
+
263
+ ```bash
264
+ python -m build
265
+ python -m twine check dist/*
266
+ ```
267
+
268
+ ## Repository Layout
269
+
270
+ - `delphiast/` - parser, preprocessor, semantic model, workspace indexer, and
271
+ LSP server
272
+ - `scripts/` - release evidence, cache checks, opencode probes, and bootstrap
273
+ helpers
274
+ - `tests/` - parser, semantic, workspace, diagnostics, packaging, and LSP tests
275
+ - `tests/fixtures/` - Delphi/Object Pascal fixtures and legacy DelphiAST snippets
276
+
277
+ ## License
278
+
279
+ This project is licensed under the Mozilla Public License 2.0. See
280
+ [`LICENSE`](LICENSE).
@@ -0,0 +1,23 @@
1
+ delphiast/__init__.py,sha256=_xUB7JH_ll0b5RSnpPPveDa82GMUeLW8P-Z8bQ7J_HY,3132
2
+ delphiast/binary.py,sha256=40mEpE0SBYmifwEoNHwSU7IU_D2usVyWYWHJ73aDfoo,8529
3
+ delphiast/comment_builder.py,sha256=XvFW7WTbYy7yGN3MG40gtabTvhGj1ea1MYkySDbVMDA,832
4
+ delphiast/consts.py,sha256=qM40iwxXmvEileWHJVLMYlQSkLoLyQaqM-Zscz3VJbs,5958
5
+ delphiast/grammar.py,sha256=QWEMSUKr0e1xlJOveZtp7K-MvhlkvDgxkD0iUKIyxKU,18577
6
+ delphiast/lark_builder.py,sha256=KTXxr5WrLMvzdb0F71JMzFqExLiEPF1_cwGrZkurgqQ,118651
7
+ delphiast/lark_tokens.py,sha256=xT_GES2z6p1IdN3EnoKE9RcUM6suu1RV3J-0FEJN2r8,4176
8
+ delphiast/lsp_server.py,sha256=Gyj3Vyi2PK-llGDpbzXipN8Jh-k4nR9Z4gEIk7sqZFY,55848
9
+ delphiast/nodes.py,sha256=LW8KUgNCg9MHK_2NmzrgCMMJNcBwa4C76XwKoS7kt4E,13556
10
+ delphiast/parser.py,sha256=4hckHD_fsiiUe3oeSRJwqbY8bDWqJaYLsU10NYDIW6E,6025
11
+ delphiast/preprocessor.py,sha256=X58ZUfI7Hifg-7KYomOF0BZEp-jl1NFGyNl9VCu6wtU,29935
12
+ delphiast/project_indexer.py,sha256=DuEx9Nb8-DLHes9xis-G4fj9xxG3r0fbXziODzu95Bc,10946
13
+ delphiast/semantic.py,sha256=egQ-_AjmnAATvL1hRUfjd4FDapFxdwi5rt8QExb0POc,9818
14
+ delphiast/semantic_builder.py,sha256=ulA7xHJpltjc6cTo5wh9yTcYazuIykM-YLMXD1h-wzk,58680
15
+ delphiast/source_reader.py,sha256=8QDSNfoVHw5yCGxyx9m8RnYK4TOuA60abA7zElv6pDg,434
16
+ delphiast/workspace.py,sha256=fm7eoT-ydmnIIMnPg9i46EuIssRWLrdAnunByYRBeeA,1750
17
+ delphiast/writer.py,sha256=j-cnyiHFNOTFECL7Ehm-m43ye7ev7qFeCCscFMjrAOY,2503
18
+ python_delphi_lsp-1.0.0.dist-info/licenses/LICENSE,sha256=-rPda9qyJvHAhjCx3ZF-Efy07F4eAg4sFvg6ChOGPoU,16726
19
+ python_delphi_lsp-1.0.0.dist-info/METADATA,sha256=Jwb5Y2dZI_JLi5eFZcRb9jfK_5F3eS8c4B4D-NMS-V8,8436
20
+ python_delphi_lsp-1.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
21
+ python_delphi_lsp-1.0.0.dist-info/entry_points.txt,sha256=717MgJShYC9J2yZ7b-LCFdEnekxgzCYt5VcyoiGKxOI,57
22
+ python_delphi_lsp-1.0.0.dist-info/top_level.txt,sha256=QN4e-RYdERIS9p1qljv_D2llVQtw0pOaN7tvIxZhLSU,10
23
+ python_delphi_lsp-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ delphi-lsp = delphiast.lsp_server:main